@chosengeneration/light-code 0.9.0 → 0.12.1

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.js CHANGED
@@ -49457,7 +49457,45 @@ var expertConfigSchema = external_exports.object({
49457
49457
  * The backstop for when the CLI reports no cost — a spend limit cannot count what it is
49458
49458
  * not told the price of, and an unpriced consultation still costs money.
49459
49459
  */
49460
- maxConsultations: external_exports.number().int().min(0)
49460
+ maxConsultations: external_exports.number().int().min(0),
49461
+ /**
49462
+ * Whether this plan reports a per-consultation cost.
49463
+ *
49464
+ * Learned rather than configured, and learned from real consultations rather than from a
49465
+ * probe — asking the CLI "do you report cost?" means making a call, and the first call in a
49466
+ * session is the expensive one. So it is recorded the first time a consultation comes back
49467
+ * with or without `total_cost_usd`.
49468
+ *
49469
+ * Absent means not yet known. It matters because a spend cap cannot bind on a plan that
49470
+ * reports no cost: `usd` stays zero, the limit is never reached, and the only control that
49471
+ * actually holds is the consultation count. A cap that silently never fires is worse than no
49472
+ * cap, because it is believed.
49473
+ */
49474
+ reportsCost: external_exports.boolean(),
49475
+ /**
49476
+ * Refresh the expert's cache while a task is open, rather than paying a cold start later.
49477
+ *
49478
+ * The cache is one hour and that TTL is Anthropic's, not ours. A trivial resumed consultation
49479
+ * before it lapses costs about a fiftieth of the cold start it avoids.
49480
+ *
49481
+ * Off by default, and it must stay that way: it spends with nobody at the screen, which is
49482
+ * the one property this product is careful about everywhere else. Its cost is counted in the
49483
+ * meter like anything else.
49484
+ */
49485
+ keepAlive: external_exports.boolean(),
49486
+ /**
49487
+ * What a consultation costs on this plan, measured rather than assumed.
49488
+ *
49489
+ * The published figures came from one plan on one day. An enterprise agreement, a
49490
+ * subscription or a gateway can each report something different — and those numbers are what
49491
+ * the budget is set from and what the expert is told when it plans to fit.
49492
+ */
49493
+ pricing: external_exports.object({
49494
+ coldUsd: external_exports.number().min(0).optional(),
49495
+ resumedUsd: external_exports.number().min(0).optional(),
49496
+ measuredAt: external_exports.number(),
49497
+ reportsCost: external_exports.boolean()
49498
+ })
49461
49499
  }).partial();
49462
49500
  var vectorStoreKindSchema = external_exports.enum(["opensearch", "qdrant", "chroma"]);
49463
49501
  var vectorStoreSchema = external_exports.object({
@@ -49822,6 +49860,21 @@ function describeSubmission(request) {
49822
49860
  ].join("\n");
49823
49861
  }
49824
49862
 
49863
+ // ../../packages/core/dist/expert/pricing.js
49864
+ var PRICING_PROBE = "Reply with the single word: OK";
49865
+ function pricingForPrompt(pricing) {
49866
+ if (pricing === void 0 || !pricing.reportsCost)
49867
+ return void 0;
49868
+ const cold = pricing.coldUsd;
49869
+ const resumed = pricing.resumedUsd;
49870
+ if (cold === void 0 || resumed === void 0)
49871
+ return void 0;
49872
+ return `Measured on this deployment: the first consultation of a task costs about ${money(cold)}, and each one after it about ${money(resumed)} because it resumes the same session. Plan accordingly \u2014 make the first one carry the task, and do not repeat context afterwards.`;
49873
+ }
49874
+ function money(value) {
49875
+ return value >= 0.01 ? `$${value.toFixed(2)}` : `$${value.toFixed(4)}`;
49876
+ }
49877
+
49825
49878
  // ../../packages/core/dist/guide/steps.js
49826
49879
  var GUIDE_STEPS = [
49827
49880
  {
@@ -52647,7 +52700,7 @@ function buildExpertBriefing(input) {
52647
52700
  }
52648
52701
 
52649
52702
  // ../../packages/core/dist/expert/budget.js
52650
- function money(value) {
52703
+ function money2(value) {
52651
52704
  return `$${value.toFixed(value < 1 ? 4 : 2)}`;
52652
52705
  }
52653
52706
  function checkExpertBudget(spend, limits) {
@@ -52662,7 +52715,7 @@ function checkExpertBudget(spend, limits) {
52662
52715
  if (maxSpend > 0 && spend.usd >= maxSpend) {
52663
52716
  return {
52664
52717
  allowed: false,
52665
- message: `The expert spending limit for this task has been reached (${money(spend.usd)} of ${money(maxSpend)}). Continue on your own: use what the expert has already told you, read the code directly, and say plainly if you are stuck rather than guessing. The user can raise the limit in Settings \u2192 Expert, or start a new task to reset it.` + (spend.unpriced > 0 ? ` Note ${String(spend.unpriced)} consultation${spend.unpriced === 1 ? "" : "s"} reported no cost, so the real total is higher than the figure above.` : "")
52718
+ message: `The expert spending limit for this task has been reached (${money2(spend.usd)} of ${money2(maxSpend)}). Continue on your own: use what the expert has already told you, read the code directly, and say plainly if you are stuck rather than guessing. The user can raise the limit in Settings \u2192 Expert, or start a new task to reset it.` + (spend.unpriced > 0 ? ` Note ${String(spend.unpriced)} consultation${spend.unpriced === 1 ? "" : "s"} reported no cost, so the real total is higher than the figure above.` : "")
52666
52719
  };
52667
52720
  }
52668
52721
  return { allowed: true };
@@ -52679,7 +52732,7 @@ function expertBudgetUsage(spend, limits) {
52679
52732
  return void 0;
52680
52733
  return Math.min(1, Math.max(...fractions));
52681
52734
  }
52682
- function describeExpertBudget(spend, limits) {
52735
+ function describeExpertBudget(spend, limits, pricing) {
52683
52736
  const parts = [];
52684
52737
  const maxConsultations = limits.maxConsultations ?? 0;
52685
52738
  if (maxConsultations > 0) {
@@ -52688,11 +52741,14 @@ function describeExpertBudget(spend, limits) {
52688
52741
  }
52689
52742
  const maxSpend = limits.maxSpendUsd ?? 0;
52690
52743
  if (maxSpend > 0) {
52691
- parts.push(`${money(Math.max(0, maxSpend - spend.usd))} of ${money(maxSpend)} left`);
52744
+ parts.push(`${money2(Math.max(0, maxSpend - spend.usd))} of ${money2(maxSpend)} left`);
52692
52745
  }
52693
52746
  if (parts.length === 0)
52694
- return void 0;
52695
- return `Budget for this task: ${parts.join(", ")}. Plan the number of checkpoints to fit \u2014 when it runs out the junior finishes alone.`;
52747
+ return pricing;
52748
+ return [
52749
+ `Budget for this task: ${parts.join(", ")}. Plan the number of checkpoints to fit \u2014 when it runs out the junior finishes alone.`,
52750
+ pricing
52751
+ ].filter((line) => line !== void 0).join(" ");
52696
52752
  }
52697
52753
 
52698
52754
  // ../../packages/core/dist/rag/vectorStore.js
@@ -63340,11 +63396,12 @@ function wireChatBridge(services) {
63340
63396
  }
63341
63397
  const pendingPathApprovals = /* @__PURE__ */ new Map();
63342
63398
  const searchLog = new SearchLog(50, () => post({ type: "searchLog", entries: [...searchLog.list()] }));
63343
- let expertSpend = { usd: 0, consultations: 0, unpriced: 0 };
63399
+ let expertSpend = { usd: 0, consultations: 0, unpriced: 0, keepAlives: 0 };
63344
63400
  let expertSessionId;
63345
63401
  function resetExpertSpend() {
63346
- expertSpend = { usd: 0, consultations: 0, unpriced: 0 };
63402
+ expertSpend = { usd: 0, consultations: 0, unpriced: 0, keepAlives: 0 };
63347
63403
  expertSessionId = void 0;
63404
+ stopKeepAlive();
63348
63405
  taskExpertLimits = void 0;
63349
63406
  taskExpertEstimate = void 0;
63350
63407
  postExpertSpend();
@@ -63370,9 +63427,24 @@ function wireChatBridge(services) {
63370
63427
  else
63371
63428
  expertSpend.unpriced += 1;
63372
63429
  postExpertSpend();
63430
+ if (info.isError)
63431
+ return;
63432
+ const learned = info.costUsd !== void 0;
63433
+ if (cachedReportsCost === learned)
63434
+ return;
63435
+ cachedReportsCost = learned;
63436
+ void configManager.load().then(async ({ config: config2 }) => {
63437
+ await configManager.save("user", { ...config2, expert: { ...config2.expert, reportsCost: learned } });
63438
+ await postExpert();
63439
+ }).catch(() => {
63440
+ });
63373
63441
  }
63374
63442
  let cachedModeId;
63375
63443
  let cachedCodeGenerator;
63444
+ let cachedReportsCost;
63445
+ let measuringStep;
63446
+ let cachedPricing;
63447
+ let cachedKeepAlive = false;
63376
63448
  let cachedProgrammingProfileId;
63377
63449
  async function loadSettings() {
63378
63450
  const { config: config2 } = await configManager.load();
@@ -63384,6 +63456,11 @@ function wireChatBridge(services) {
63384
63456
  cachedAccentColor = config2.ui?.accentColor ?? "#22C55E";
63385
63457
  cachedExpertColor = config2.ui?.expertColor ?? "#D97757";
63386
63458
  cachedAssessment = config2.expert?.assessment;
63459
+ cachedReportsCost = config2.expert?.reportsCost;
63460
+ cachedPricing = config2.expert?.pricing;
63461
+ cachedKeepAlive = config2.expert?.keepAlive === true;
63462
+ if (!cachedKeepAlive)
63463
+ stopKeepAlive();
63387
63464
  cachedExpertLimits = {
63388
63465
  ...config2.expert?.maxSpendUsd !== void 0 ? { maxSpendUsd: config2.expert.maxSpendUsd } : {},
63389
63466
  ...config2.expert?.maxConsultations !== void 0 ? { maxConsultations: config2.expert.maxConsultations } : {}
@@ -63406,12 +63483,13 @@ function wireChatBridge(services) {
63406
63483
  expertColor: cachedExpertColor,
63407
63484
  readRoots: cachedReadRoots,
63408
63485
  ...cachedProgrammingProfileId !== void 0 ? { programmingProfileId: cachedProgrammingProfileId } : {},
63409
- ...guideCapability()
63486
+ ...hostCapabilities()
63410
63487
  });
63411
63488
  }
63412
- function guideCapability() {
63489
+ function hostCapabilities() {
63413
63490
  return {
63414
63491
  nativeGuide: ui.openWalkthrough !== void 0,
63492
+ allowProgrammingProfile: services.allowProgrammingProfile === true,
63415
63493
  ...services.guideMediaBase !== void 0 ? { guideMediaBase: services.guideMediaBase } : {}
63416
63494
  };
63417
63495
  }
@@ -63521,7 +63599,11 @@ function wireChatBridge(services) {
63521
63599
  // Read at call time, not captured: the user can raise the limit mid-task and the very
63522
63600
  // next consultation should honour it, without starting a new task to pick it up.
63523
63601
  budget: () => checkExpertBudget(expertSpend, effectiveExpertLimits()),
63524
- budgetSummary: () => describeExpertBudget(expertSpend, effectiveExpertLimits()),
63602
+ /*
63603
+ * The measured cost goes with the budget, so the expert plans in this deployment's
63604
+ * units rather than from what it believes consultations cost in general.
63605
+ */
63606
+ budgetSummary: () => describeExpertBudget(expertSpend, effectiveExpertLimits(), pricingForPrompt(cachedPricing)),
63525
63607
  onEstimate: (estimate) => {
63526
63608
  taskExpertEstimate = estimate;
63527
63609
  postExpertSpend();
@@ -63530,6 +63612,8 @@ function wireChatBridge(services) {
63530
63612
  get: () => expertSessionId,
63531
63613
  set: (sessionId) => {
63532
63614
  expertSessionId = sessionId;
63615
+ if (sessionId !== void 0 && cachedKeepAlive)
63616
+ ensureKeepAlive();
63533
63617
  }
63534
63618
  },
63535
63619
  /*
@@ -64097,18 +64181,29 @@ function wireChatBridge(services) {
64097
64181
  logger.warn(`could not check the expert CLI: ${reason}`);
64098
64182
  const settings = await configManager.load().then((loaded) => loaded.config.expert, () => void 0);
64099
64183
  post({
64100
- type: "expert",
64101
- enabled: settings?.enabled === true,
64184
+ ...expertMessageFrom(settings),
64102
64185
  available: false,
64103
64186
  path: settings?.path ?? expertCliPath ?? "claude",
64104
- reason: `Could not check whether the Claude CLI is available: ${reason}`,
64105
- ...settings?.model !== void 0 ? { model: settings.model } : {},
64106
- maxSpendUsd: settings?.maxSpendUsd ?? 0,
64107
- maxConsultations: settings?.maxConsultations ?? 0,
64108
- ...settings?.assessment !== void 0 ? { assessment: settings.assessment } : {}
64187
+ reason: `Could not check whether the Claude CLI is available: ${reason}`
64109
64188
  });
64110
64189
  }
64111
64190
  }
64191
+ function expertMessageFrom(settings) {
64192
+ return {
64193
+ type: "expert",
64194
+ enabled: settings?.enabled === true,
64195
+ available: false,
64196
+ path: settings?.path ?? expertCliPath ?? "claude",
64197
+ maxSpendUsd: settings?.maxSpendUsd ?? 0,
64198
+ maxConsultations: settings?.maxConsultations ?? 0,
64199
+ keepAlive: settings?.keepAlive === true,
64200
+ ...settings?.model !== void 0 ? { model: settings.model } : {},
64201
+ ...settings?.assessment !== void 0 ? { assessment: settings.assessment } : {},
64202
+ ...settings?.reportsCost !== void 0 ? { reportsCost: settings.reportsCost } : {},
64203
+ ...settings?.pricing !== void 0 ? { pricing: settings.pricing } : {},
64204
+ ...measuringStep !== void 0 ? { measuringStep } : {}
64205
+ };
64206
+ }
64112
64207
  async function postExpertInner(redetect) {
64113
64208
  const { config: config2 } = await configManager.load();
64114
64209
  const configured = config2.expert?.path ?? "claude";
@@ -64116,19 +64211,125 @@ function wireChatBridge(services) {
64116
64211
  expertCli = detected;
64117
64212
  expertCliPath = configured;
64118
64213
  post({
64119
- type: "expert",
64120
- enabled: config2.expert?.enabled === true,
64214
+ // Everything from settings comes from one place, so the two paths cannot drift again.
64215
+ ...expertMessageFrom(config2.expert),
64121
64216
  available: detected.available,
64122
64217
  path: configured,
64123
64218
  ...detected.version !== void 0 ? { version: detected.version } : {},
64124
64219
  ...detected.reason !== void 0 ? { reason: detected.reason } : {},
64125
- ...config2.expert?.model !== void 0 ? { model: config2.expert.model } : {},
64126
- maxSpendUsd: config2.expert?.maxSpendUsd ?? 0,
64127
- maxConsultations: config2.expert?.maxConsultations ?? 0,
64128
- ...config2.expert?.assessment !== void 0 ? { assessment: config2.expert.assessment } : {},
64129
64220
  ...assessmentStep === void 0 ? {} : { assessing: true, assessmentStep }
64130
64221
  });
64131
64222
  }
64223
+ const KEEP_ALIVE_MS = 50 * 60 * 1e3;
64224
+ let keepAliveTimer;
64225
+ function stopKeepAlive() {
64226
+ if (keepAliveTimer === void 0)
64227
+ return;
64228
+ clearInterval(keepAliveTimer);
64229
+ keepAliveTimer = void 0;
64230
+ }
64231
+ function ensureKeepAlive() {
64232
+ if (keepAliveTimer !== void 0)
64233
+ return;
64234
+ keepAliveTimer = setInterval(() => {
64235
+ void runKeepAlive();
64236
+ }, KEEP_ALIVE_MS);
64237
+ keepAliveTimer.unref?.();
64238
+ }
64239
+ async function runKeepAlive() {
64240
+ const session = expertSessionId;
64241
+ if (session === void 0) {
64242
+ stopKeepAlive();
64243
+ return;
64244
+ }
64245
+ try {
64246
+ const { config: config2 } = await configManager.load();
64247
+ if (config2.expert?.keepAlive !== true) {
64248
+ stopKeepAlive();
64249
+ return;
64250
+ }
64251
+ const verdict = checkExpertBudget(expertSpend, effectiveExpertLimits());
64252
+ if (!verdict.allowed) {
64253
+ logger.info("expert keep-alive stopped: the budget for this task is spent");
64254
+ stopKeepAlive();
64255
+ return;
64256
+ }
64257
+ const cli = await resolveExpert(config2);
64258
+ if (cli === void 0) {
64259
+ stopKeepAlive();
64260
+ return;
64261
+ }
64262
+ const answer = await consultExpert(cli, {
64263
+ question: PRICING_PROBE,
64264
+ cwd: workspaceRoot ?? process.cwd(),
64265
+ ...config2.expert?.model !== void 0 ? { model: config2.expert.model } : {},
64266
+ resumeSessionId: session
64267
+ }, logger);
64268
+ expertSpend.keepAlives += 1;
64269
+ if (answer.costUsd !== void 0)
64270
+ expertSpend.usd += answer.costUsd;
64271
+ if (answer.sessionId !== void 0)
64272
+ expertSessionId = answer.sessionId;
64273
+ postExpertSpend();
64274
+ logger.info("expert keep-alive refreshed the session cache");
64275
+ } catch (error51) {
64276
+ logger.warn(`expert keep-alive failed: ${String(error51)}`);
64277
+ }
64278
+ }
64279
+ async function handleMeasureExpertCost() {
64280
+ if (measuringStep !== void 0) {
64281
+ post({ type: "error", message: `Already measuring \u2014 ${measuringStep}` });
64282
+ return;
64283
+ }
64284
+ measuringStep = "Starting\u2026";
64285
+ logger.info("measuring what an expert consultation costs");
64286
+ await postExpert({ redetect: false });
64287
+ try {
64288
+ const { config: config2 } = await configManager.load();
64289
+ const cli = await resolveExpert(config2);
64290
+ if (cli === void 0) {
64291
+ post({
64292
+ type: "error",
64293
+ message: "The Claude CLI could not be found, so there is nothing to measure. Check the path in this tab."
64294
+ });
64295
+ return;
64296
+ }
64297
+ let sessionId;
64298
+ const samples = [];
64299
+ for (const [index, label] of ["first consultation", "follow-up in the same session"].entries()) {
64300
+ measuringStep = `Measuring the ${label} (${String(index + 1)}/2)\u2026`;
64301
+ await postExpert({ redetect: false });
64302
+ const answer = await consultExpert(cli, {
64303
+ question: PRICING_PROBE,
64304
+ cwd: workspaceRoot ?? process.cwd(),
64305
+ ...config2.expert?.model !== void 0 ? { model: config2.expert.model } : {},
64306
+ // Cold on the first pass, resumed on the second. That pair is the measurement.
64307
+ ...sessionId !== void 0 ? { resumeSessionId: sessionId } : {}
64308
+ }, logger);
64309
+ samples.push(answer.costUsd);
64310
+ sessionId = answer.sessionId ?? sessionId;
64311
+ }
64312
+ const [cold, resumed] = samples;
64313
+ const reportsCost = cold !== void 0 || resumed !== void 0;
64314
+ const pricing = {
64315
+ measuredAt: Date.now(),
64316
+ reportsCost,
64317
+ ...cold !== void 0 ? { coldUsd: cold } : {},
64318
+ ...resumed !== void 0 ? { resumedUsd: resumed } : {}
64319
+ };
64320
+ const { config: current } = await configManager.load();
64321
+ await configManager.save("user", {
64322
+ ...current,
64323
+ expert: { ...current.expert, pricing, reportsCost }
64324
+ });
64325
+ logger.info(reportsCost ? `expert pricing measured: cold ${String(cold)} / resumed ${String(resumed)}` : "expert pricing measured: this plan reports no cost per consultation");
64326
+ } catch (error51) {
64327
+ post({ type: "error", message: `Could not measure the expert's cost: ${String(error51)}` });
64328
+ } finally {
64329
+ measuringStep = void 0;
64330
+ await postExpert({ redetect: false });
64331
+ }
64332
+ }
64132
64333
  async function handleAssessJunior() {
64133
64334
  if (assessmentStep !== void 0)
64134
64335
  return;
@@ -64956,6 +65157,8 @@ function wireChatBridge(services) {
64956
65157
  }
64957
65158
  }
64958
65159
  function codeGeneratorFor(config2) {
65160
+ if (services.allowProgrammingProfile !== true)
65161
+ return void 0;
64959
65162
  const id = config2.programmingProfileId;
64960
65163
  if (id === void 0 || id.length === 0)
64961
65164
  return void 0;
@@ -64987,7 +65190,7 @@ function wireChatBridge(services) {
64987
65190
  accentColor: cachedAccentColor,
64988
65191
  expertColor: cachedExpertColor,
64989
65192
  readRoots: cachedReadRoots,
64990
- ...guideCapability()
65193
+ ...hostCapabilities()
64991
65194
  });
64992
65195
  }
64993
65196
  async function handleAlwaysAllow(id, scope) {
@@ -65390,6 +65593,21 @@ function wireChatBridge(services) {
65390
65593
  ...message.maxConsultations !== void 0 ? { maxConsultations: message.maxConsultations } : {}
65391
65594
  };
65392
65595
  postExpertSpend();
65596
+ } else if (message.type === "setExpertKeepAlive") {
65597
+ void configManager.load().then(async ({ config: config2 }) => {
65598
+ await configManager.save("user", { ...config2, expert: { ...config2.expert, keepAlive: message.enabled } });
65599
+ await postExpert({ redetect: false });
65600
+ }).catch((error51) => post({ type: "error", message: String(error51) }));
65601
+ } else if (message.type === "measureExpertCost") {
65602
+ void handleMeasureExpertCost();
65603
+ } else if (message.type === "clearExpertPricing") {
65604
+ void configManager.load().then(async ({ config: config2 }) => {
65605
+ const expert = { ...config2.expert };
65606
+ delete expert.pricing;
65607
+ delete expert.reportsCost;
65608
+ await configManager.save("user", { ...config2, expert });
65609
+ await postExpert({ redetect: false });
65610
+ }).catch((error51) => post({ type: "error", message: String(error51) }));
65393
65611
  } else if (message.type === "assessJunior") {
65394
65612
  void handleAssessJunior();
65395
65613
  } else if (message.type === "clearAssessment") {
@@ -65859,6 +66077,7 @@ ${entry.content}`);
65859
66077
  clearTimeout(docsReindexTimer);
65860
66078
  if (scheduleTimer !== void 0)
65861
66079
  clearInterval(scheduleTimer);
66080
+ stopKeepAlive();
65862
66081
  unsubscribe();
65863
66082
  }
65864
66083
  };
@@ -67320,6 +67539,12 @@ async function createSession(options) {
67320
67539
  * also survives whatever port the server happened to bind.
67321
67540
  */
67322
67541
  guideMediaBase: "/guide",
67542
+ /*
67543
+ * Offered here and nowhere else. A shared server is where "a cheap model chats, a good one
67544
+ * writes the code" is worth configuring — and where an administrator can set a default for
67545
+ * people who have not chosen.
67546
+ */
67547
+ allowProgrammingProfile: true,
67323
67548
  ...options.submitForReview !== void 0 ? { submitForReview: options.submitForReview } : {},
67324
67549
  /*
67325
67550
  * Resolved per read, so both halves stay live — an administrator's edit and the user's own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chosengeneration/light-code",
3
- "version": "0.9.0",
3
+ "version": "0.12.1",
4
4
  "description": "A minimal agentic coding assistant, served to your browser from a local Node server",
5
5
  "license": "MIT",
6
6
  "type": "module",