@genesislcap/ai-assistant 15.10.4 → 15.10.6

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.
@@ -5256,6 +5256,73 @@
5256
5256
  "isAbstract": false,
5257
5257
  "name": "markAsSubAgent"
5258
5258
  },
5259
+ {
5260
+ "kind": "Method",
5261
+ "canonicalReference": "@genesislcap/ai-assistant!ChatDriver#reportBudgetExhausted:member(1)",
5262
+ "docComment": "",
5263
+ "excerptTokens": [
5264
+ {
5265
+ "kind": "Content",
5266
+ "text": "reportBudgetExhausted(e: "
5267
+ },
5268
+ {
5269
+ "kind": "Reference",
5270
+ "text": "BudgetExhaustedError",
5271
+ "canonicalReference": "@genesislcap/foundation-ai!BudgetExhaustedError:class"
5272
+ },
5273
+ {
5274
+ "kind": "Content",
5275
+ "text": ", pendingUserMessage?: "
5276
+ },
5277
+ {
5278
+ "kind": "Reference",
5279
+ "text": "ChatMessage",
5280
+ "canonicalReference": "@genesislcap/foundation-ai!ChatMessage:interface"
5281
+ },
5282
+ {
5283
+ "kind": "Content",
5284
+ "text": "): "
5285
+ },
5286
+ {
5287
+ "kind": "Reference",
5288
+ "text": "ChatDriverResult",
5289
+ "canonicalReference": "@genesislcap/foundation-ai!ChatDriverResult:type"
5290
+ },
5291
+ {
5292
+ "kind": "Content",
5293
+ "text": ";"
5294
+ }
5295
+ ],
5296
+ "isStatic": false,
5297
+ "returnTypeTokenRange": {
5298
+ "startIndex": 5,
5299
+ "endIndex": 6
5300
+ },
5301
+ "releaseTag": "Beta",
5302
+ "isProtected": false,
5303
+ "overloadIndex": 1,
5304
+ "parameters": [
5305
+ {
5306
+ "parameterName": "e",
5307
+ "parameterTypeTokenRange": {
5308
+ "startIndex": 1,
5309
+ "endIndex": 2
5310
+ },
5311
+ "isOptional": false
5312
+ },
5313
+ {
5314
+ "parameterName": "pendingUserMessage",
5315
+ "parameterTypeTokenRange": {
5316
+ "startIndex": 3,
5317
+ "endIndex": 4
5318
+ },
5319
+ "isOptional": true
5320
+ }
5321
+ ],
5322
+ "isOptional": false,
5323
+ "isAbstract": false,
5324
+ "name": "reportBudgetExhausted"
5325
+ },
5259
5326
  {
5260
5327
  "kind": "Method",
5261
5328
  "canonicalReference": "@genesislcap/ai-assistant!ChatDriver#requestInteraction:member(1)",
@@ -5692,7 +5759,7 @@
5692
5759
  {
5693
5760
  "kind": "PropertySignature",
5694
5761
  "canonicalReference": "@genesislcap/ai-assistant!ChatDriverConfig#budgetExhaustedMessage:member",
5695
- "docComment": "/**\n * Transcript copy appended when the AI-spend budget wall is hit (GENC-1464). Defaults to `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`.\n *\n * The assistant element already lets a host override the blocked **banner** via `setBlocked(true, reason)`; without this the transcript **bubble** stayed on the default, so a white-labelled host got its own copy in the banner and the shipped default directly below it. Passing the same effective copy here keeps the two surfaces saying one thing.\n *\n * Deliberately a driver-config field rather than something read off a chat config: `ChatDriver` has no `chatConfig` and is used standalone (see `chat-driver-node`), so threading one in would be a much larger and less reversible change.\n */\n",
5762
+ "docComment": "/**\n * Transcript copy appended when the AI-spend budget wall is hit (GENC-1464), used VERBATIM — set it only to own the copy entirely (white-labelling). When unset, the driver composes the bubble per wall: the neutral `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`, upgraded with \"switch to another AI provider\" advice only when the 402's `otherVendorAvailable` positively says another metered vendor still has headroom — the same rule the blocked banner applies, so the two surfaces can no longer contradict each other.\n *\n * The assistant element already lets a host override the blocked **banner** via `setBlocked(true, reason)`; without this the transcript **bubble** stayed on the default, so a white-labelled host got its own copy in the banner and the shipped default directly below it. Passing the same effective copy here keeps the two surfaces saying one thing.\n *\n * Deliberately a driver-config field rather than something read off a chat config: `ChatDriver` has no `chatConfig` and is used standalone (see `chat-driver-node`), so threading one in would be a much larger and less reversible change.\n */\n",
5696
5763
  "excerptTokens": [
5697
5764
  {
5698
5765
  "kind": "Content",
@@ -1492,8 +1492,12 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1492
1492
  private readonly sessionKey;
1493
1493
  /** Injected activity bus; defaults to a no-op off-browser (Node/tests/headless). */
1494
1494
  private readonly activityBus;
1495
- /** Transcript copy for a budget wall — see `ChatDriverConfig.budgetExhaustedMessage`. */
1496
- private readonly budgetExhaustedMessage;
1495
+ /**
1496
+ * Host override for the budget-wall transcript copy, used verbatim — see
1497
+ * `ChatDriverConfig.budgetExhaustedMessage`. `undefined` means no override,
1498
+ * and the bubble is composed per wall by {@link ChatDriver.budgetExhaustedBubble}.
1499
+ */
1500
+ private readonly budgetExhaustedMessageOverride?;
1497
1501
  /**
1498
1502
  * Set the moment a budget wall is observed anywhere in this turn — this
1499
1503
  * driver's own 402, or a sub-agent's (which surfaces here only as a
@@ -1597,6 +1601,7 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1597
1601
  *
1598
1602
  * @internal
1599
1603
  */
1604
+ private budgetExhaustedBubble;
1600
1605
  reportBudgetExhausted(e: BudgetExhaustedError, pendingUserMessage?: ChatMessage): ChatDriverResult;
1601
1606
  /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
1602
1607
  private static failureReasonOf;
@@ -1914,8 +1919,13 @@ export declare interface ChatDriverConfig {
1914
1919
  */
1915
1920
  activityBus?: ActivityBus;
1916
1921
  /**
1917
- * Transcript copy appended when the AI-spend budget wall is hit (GENC-1464).
1918
- * Defaults to `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`.
1922
+ * Transcript copy appended when the AI-spend budget wall is hit (GENC-1464),
1923
+ * used VERBATIM — set it only to own the copy entirely (white-labelling).
1924
+ * When unset, the driver composes the bubble per wall: the neutral
1925
+ * `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`, upgraded with "switch to another AI
1926
+ * provider" advice only when the 402's `otherVendorAvailable` positively says
1927
+ * another metered vendor still has headroom — the same rule the blocked
1928
+ * banner applies, so the two surfaces can no longer contradict each other.
1919
1929
  *
1920
1930
  * The assistant element already lets a host override the blocked **banner**
1921
1931
  * via `setBlocked(true, reason)`; without this the transcript **bubble** stayed
@@ -2859,24 +2869,26 @@ export declare class FoundationAiAssistant extends GenesisElement {
2859
2869
  */
2860
2870
  get effectiveBlockedReason(): string;
2861
2871
  /**
2862
- * Copy the driver writes into the TRANSCRIPT when a turn hits the budget wall.
2863
- *
2864
- * A host-set `blockedReason` still wins, so a white-labelled host does not read
2865
- * its own explanation in the banner and the shipped default directly below it —
2866
- * that is the whole reason the driver takes this at all.
2867
- *
2868
- * What it deliberately is NOT is
2869
- * {@link FoundationAiAssistant.effectiveBlockedReason}. That getter
2870
- * composes advice `"…Switch to Gemini to keep going."` — which is true only
2871
- * while that vendor has headroom, and the transcript is a permanent record. The
2872
- * driver reads this once at CONSTRUCTION, and `getOrCreateDriver` keys on the
2873
- * agents list, so an agents swap *after* a wall re-runs `createDriver` and would
2874
- * freeze that sentence into every later turn's bubble. Reading the latch's own
2875
- * copy instead is structurally incapable of carrying the advice: the only
2876
- * writer of `blockedReason` is the vendor-agnostic branch of
2877
- * `latchBlockedFrom`, whose `formatBlockedReason(budget)` form has no switch
2878
- * clause. Per-vendor copy lives in `blockedVendorReasons` and reaches the
2879
- * banner alone.
2872
+ * Copy the driver writes into the TRANSCRIPT when a turn hits the budget wall,
2873
+ * or `undefined` to let the driver compose it per wall.
2874
+ *
2875
+ * A set `blockedReason` still wins a white-labelled host must not read its
2876
+ * own explanation in the banner and shipped copy directly below it, and the
2877
+ * vendor-agnostic latch's `formatBlockedReason(budget)` form stays advice-free
2878
+ * exactly as before.
2879
+ *
2880
+ * `undefined` (the usual case) is NOT the old `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`
2881
+ * fallback: it hands composition to the driver, which builds the bubble from
2882
+ * EACH wall's own 402 naming the exhausted vendor and advising a switch only
2883
+ * when that refusal's `otherVendorAvailable` positively vouched for headroom
2884
+ * elsewhere. That is immune to the staleness this getter used to guard against
2885
+ * by freezing the copy: construction-time advice could outlive its truth
2886
+ * (`getOrCreateDriver` re-runs on an agents swap and would have baked the
2887
+ * sentence into every later bubble), but per-wall advice is derived from the
2888
+ * refusal it annotates and dies with the turn. Forcing the default here was
2889
+ * also what made the driver's composed bubble unreachable in every real
2890
+ * mount — the banner advised switching while the bubble under it never did
2891
+ * (the GENC-1464 tester finding, root cause).
2880
2892
  */
2881
2893
  private get transcriptBudgetExhaustedMessage();
2882
2894
  /**
@@ -3573,13 +3573,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3573
3573
  maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
3574
3574
  sessionKey = "",
3575
3575
  activityBus = NOOP_ACTIVITY_BUS,
3576
- budgetExhaustedMessage = DEFAULT_BUDGET_EXHAUSTED_MESSAGE
3576
+ budgetExhaustedMessage
3577
3577
  } = config;
3578
3578
  this.maxToolIterations = maxToolIterations;
3579
3579
  this.condenseBatchCalls = condenseBatchCalls;
3580
3580
  this.sessionKey = sessionKey;
3581
3581
  this.activityBus = activityBus;
3582
- this.budgetExhaustedMessage = budgetExhaustedMessage;
3582
+ this.budgetExhaustedMessageOverride = budgetExhaustedMessage;
3583
3583
  if (typeof toolHandlers === "function") {
3584
3584
  this.toolHandlersFactory = toolHandlers;
3585
3585
  this.toolHandlers = {};
@@ -3716,6 +3716,12 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3716
3716
  *
3717
3717
  * @internal
3718
3718
  */
3719
+ budgetExhaustedBubble(wall) {
3720
+ if (this.budgetExhaustedMessageOverride != null) return this.budgetExhaustedMessageOverride;
3721
+ if (wall?.otherVendorAvailable !== true) return DEFAULT_BUDGET_EXHAUSTED_MESSAGE;
3722
+ const subject = wall?.vendorLabel ? `${wall.vendorLabel}'s AI usage limit` : "your AI usage limit";
3723
+ return `You've reached ${subject}. Switch to another AI provider in Settings to keep going, or contact your administrator to raise it.`;
3724
+ }
3719
3725
  reportBudgetExhausted(e, pendingUserMessage) {
3720
3726
  if (pendingUserMessage) this.appendToHistory(pendingUserMessage);
3721
3727
  this.budgetExhaustedThisTurn = true;
@@ -3734,7 +3740,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3734
3740
  spentUsd: e.spentUsd,
3735
3741
  isSubAgent: this.isSubAgent
3736
3742
  });
3737
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
3743
+ this.appendToHistory({ role: "assistant", content: this.budgetExhaustedBubble(e) });
3738
3744
  return this.turnDone("budget-exhausted", budgetDetailOf(e));
3739
3745
  }
3740
3746
  /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
@@ -4863,7 +4869,10 @@ Output format (strict):
4863
4869
  if (this.isSubAgent) {
4864
4870
  this.failSubAgent("budget_exhausted", this.budgetWallDetail);
4865
4871
  } else {
4866
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
4872
+ this.appendToHistory({
4873
+ role: "assistant",
4874
+ content: this.budgetExhaustedBubble(this.budgetWallDetail)
4875
+ });
4867
4876
  }
4868
4877
  return this.turnDone("budget-exhausted", this.budgetWallDetail);
4869
4878
  }