laohuang 0.8.1 → 0.8.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/bin.js CHANGED
@@ -7288,9 +7288,9 @@ var MainScreenRenderer = class _MainScreenRenderer {
7288
7288
  const width = Math.max(1, size.columns);
7289
7289
  const height = Math.max(1, size.rows);
7290
7290
  const newLines = frame.lines;
7291
- _MainScreenRenderer.#validateLines(newLines, width);
7292
7291
  const widthChanged = this.#previousWidth !== 0 && this.#previousWidth !== width;
7293
7292
  const heightChanged = this.#previousHeight !== 0 && this.#previousHeight !== height;
7293
+ _MainScreenRenderer.#validateLines(newLines, width, widthChanged || heightChanged ? [] : this.#previousLines);
7294
7294
  const previousBufferLength = this.#previousHeight > 0 ? this.#previousViewportTop + this.#previousHeight : height;
7295
7295
  const prevViewportTop = heightChanged ? Math.max(0, previousBufferLength - height) : this.#previousViewportTop;
7296
7296
  let viewportTop = prevViewportTop;
@@ -7509,8 +7509,10 @@ var MainScreenRenderer = class _MainScreenRenderer {
7509
7509
  this.#previousWidth = width;
7510
7510
  this.#previousHeight = height;
7511
7511
  }
7512
- static #validateLines(lines2, width) {
7512
+ static #validateLines(lines2, width, previous) {
7513
7513
  lines2.forEach((line2, index) => {
7514
+ if (previous[index] === line2)
7515
+ return;
7514
7516
  if (/[\r\n]/u.test(line2)) {
7515
7517
  throw new Error(`rendered line ${index} contains a physical newline`);
7516
7518
  }
@@ -7521,15 +7523,28 @@ var MainScreenRenderer = class _MainScreenRenderer {
7521
7523
  });
7522
7524
  }
7523
7525
  };
7526
+ var WIDTH_CACHE_SIZE = 512;
7527
+ var WIDTH_CACHE_MAX_TEXT_LENGTH = 4096;
7528
+ var widthCache = /* @__PURE__ */ new Map();
7524
7529
  function visibleWidth(text) {
7525
- if (!text) {
7526
- return 0;
7527
- }
7530
+ if (/^[\x20-\x7e]*$/u.test(text))
7531
+ return text.length;
7532
+ const cached = widthCache.get(text);
7533
+ if (cached !== void 0)
7534
+ return cached;
7528
7535
  const stripped = stripTerminalControls(text).replace(/\t/g, " ");
7529
7536
  let width = 0;
7530
7537
  for (const cluster of graphemeClusters(stripped)) {
7531
7538
  width += clusterWidth(cluster);
7532
7539
  }
7540
+ if (text.length <= WIDTH_CACHE_MAX_TEXT_LENGTH) {
7541
+ if (widthCache.size >= WIDTH_CACHE_SIZE) {
7542
+ const oldest = widthCache.keys().next().value;
7543
+ if (oldest !== void 0)
7544
+ widthCache.delete(oldest);
7545
+ }
7546
+ widthCache.set(text, width);
7547
+ }
7533
7548
  return width;
7534
7549
  }
7535
7550
  function truncateToWidth(text, width) {
@@ -7783,7 +7798,7 @@ function truncateStyledLine(value, width, ellipsis = "\u2026") {
7783
7798
  if (!truncated) {
7784
7799
  return line(...mergeAdjacent(result));
7785
7800
  }
7786
- const ellipsisWidth = charCellWidth(ellipsis);
7801
+ const ellipsisWidth = ellipsis === "" ? 0 : charCellWidth(ellipsis);
7787
7802
  while (result.length > 0 && used + ellipsisWidth > width) {
7788
7803
  const removed = result.pop();
7789
7804
  used -= charCellWidth(removed.text);
@@ -9161,6 +9176,10 @@ var UIEventReducer = class _UIEventReducer {
9161
9176
  payload
9162
9177
  });
9163
9178
  }
9179
+ if (kind === "ui.context_usage") {
9180
+ this.#updateContextUsage(payload);
9181
+ return createUpdate(kind, { payload });
9182
+ }
9164
9183
  if (kind === "task.state_changed") {
9165
9184
  const rawState = payload.state ?? "IDLE";
9166
9185
  this.state.sessionState = String(unwrapValue(rawState)).toUpperCase();
@@ -9761,6 +9780,23 @@ var TranscriptStore = class {
9761
9780
  };
9762
9781
 
9763
9782
  // ../../packages/terminal/tui/dist/tui/ansi-renderer.js
9783
+ var StyledLineCompiler = class {
9784
+ #cache = /* @__PURE__ */ new WeakMap();
9785
+ compile(values, width, theme) {
9786
+ return values.map((value) => {
9787
+ const cached = this.#cache.get(value);
9788
+ if (cached !== void 0 && cached.width === width && cached.theme === theme) {
9789
+ return cached.text;
9790
+ }
9791
+ const text = compileStyledLine(truncateStyledLine(value, width, ""), width, theme);
9792
+ this.#cache.set(value, { width, theme, text });
9793
+ return text;
9794
+ });
9795
+ }
9796
+ invalidate() {
9797
+ this.#cache = /* @__PURE__ */ new WeakMap();
9798
+ }
9799
+ };
9764
9800
  function compileStyledLine(value, width, theme) {
9765
9801
  const text = value.spans.map((item) => compileSpan(item, theme)).join("");
9766
9802
  const renderedWidth = visibleWidth(text);
@@ -9821,7 +9857,7 @@ var CompletionPopup = class {
9821
9857
  const description = item.description.replace(/[\r\n]+/gu, " ").trim();
9822
9858
  const value = line(span(selected ? "\u203A " : " ", selected ? { foreground: "accent" } : void 0), span(item.value, selected ? { foreground: "accent" } : void 0), ...description ? [span(` ${description}`, { foreground: "muted" })] : []);
9823
9859
  const boundedWidth = Math.max(1, width);
9824
- return displayWidth2(lineText(value)) <= boundedWidth ? value : truncateStyledLine(value, boundedWidth + 1, "");
9860
+ return displayWidth2(lineText(value)) <= boundedWidth ? value : truncateStyledLine(value, boundedWidth, "");
9825
9861
  }
9826
9862
  };
9827
9863
  function displayWidth2(value) {
@@ -9937,7 +9973,8 @@ function contextUsageLabel(tokens, contextWindow) {
9937
9973
  return null;
9938
9974
  }
9939
9975
  const used = normalizedCount(tokens);
9940
- const percent = Math.max(0, Math.min(100, Math.floor(used / window * 100)));
9976
+ const ratio = Math.min(100, used / window * 100);
9977
+ const percent = ratio > 0 && ratio < 0.1 ? "<0.1" : formatScaled(ratio);
9941
9978
  return `context: ${percent}% (${formatCompactCount(used)}/${formatCompactCount(window)})`;
9942
9979
  }
9943
9980
  function normalizedCount(value) {
@@ -10933,10 +10970,10 @@ var Transcript = class {
10933
10970
  const key = cacheKey(block);
10934
10971
  const signature = blockSignature(block);
10935
10972
  const cached = this.#cache.get(key);
10936
- if (cached !== void 0 && cached.width === context.width && cached.signature === signature) {
10973
+ if (cached !== void 0 && cached.block === block && cached.width === context.width && cached.signature === signature) {
10937
10974
  return cached.lines;
10938
10975
  }
10939
- if (cached !== void 0 && cached.signature === signature) {
10976
+ if (cached !== void 0 && cached.block === block && cached.signature === signature) {
10940
10977
  const lines3 = cached.component.render(context).lines;
10941
10978
  this.#cache.set(key, {
10942
10979
  ...cached,
@@ -10948,6 +10985,7 @@ var Transcript = class {
10948
10985
  const component = this.#createBlockComponent(block);
10949
10986
  const lines2 = component.render(context).lines;
10950
10987
  this.#cache.set(key, {
10988
+ block,
10951
10989
  component,
10952
10990
  width: context.width,
10953
10991
  signature,
@@ -11096,6 +11134,11 @@ var FrameBuilder = class {
11096
11134
  #effort;
11097
11135
  #title;
11098
11136
  #theme;
11137
+ #transcriptView;
11138
+ #lineCompiler = new StyledLineCompiler();
11139
+ #previousWidth = 0;
11140
+ #previousSources = [];
11141
+ #previousLines = [];
11099
11142
  constructor(options) {
11100
11143
  this.#state = options.state;
11101
11144
  this.#transcript = options.transcript;
@@ -11105,12 +11148,21 @@ var FrameBuilder = class {
11105
11148
  this.#effort = options.effort ?? null;
11106
11149
  this.#title = options.title ?? "laoHuang";
11107
11150
  this.#theme = options.theme ?? resolveTerminalTheme();
11151
+ this.#transcriptView = new Transcript({ blocks: this.#transcript.blocks() });
11108
11152
  }
11109
11153
  build(options) {
11110
11154
  const terminalWidth = Math.max(1, options.width);
11111
11155
  const width = Math.max(1, terminalWidth - 1);
11112
11156
  const mainScreen = options.compiledMainScreen ?? this.#fallbackMainScreen(options, width);
11113
- const lines2 = mainScreen.lines.map((value) => visibleWidth(value) <= width ? value : truncateToWidth(value, width));
11157
+ const lines2 = mainScreen.lines.map((value, index) => {
11158
+ if (this.#previousWidth === width && this.#previousSources[index] === value) {
11159
+ return this.#previousLines[index];
11160
+ }
11161
+ return visibleWidth(value) <= width ? value : truncateToWidth(value, width);
11162
+ });
11163
+ this.#previousWidth = width;
11164
+ this.#previousSources = [...mainScreen.lines];
11165
+ this.#previousLines = lines2;
11114
11166
  const cursor = {
11115
11167
  row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines2.length - 1))),
11116
11168
  col: Math.max(0, Math.min(mainScreen.cursor.column, width - 1))
@@ -11132,9 +11184,7 @@ var FrameBuilder = class {
11132
11184
  return this.#status().render({ width: Math.max(1, width), theme: this.#theme }).lines.map(lineText).join("\n");
11133
11185
  }
11134
11186
  #fallbackMainScreen(options, width) {
11135
- const transcript = new Transcript({
11136
- blocks: this.#transcript.blocks()
11137
- }).renderWithMetadata({ width, theme: this.#theme });
11187
+ const transcript = this.#transcriptView.renderWithMetadata({ width, theme: this.#theme });
11138
11188
  const transcriptLines = transcript.lines;
11139
11189
  const composer = new Composer({
11140
11190
  editor: options.editor,
@@ -11149,7 +11199,7 @@ var FrameBuilder = class {
11149
11199
  const cursor = composer.cursor ?? { row: Math.max(0, composer.lines.length - 1), column: 0 };
11150
11200
  const lines2 = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
11151
11201
  return {
11152
- lines: compileStyledLines(lines2, width, this.#theme),
11202
+ lines: this.#lineCompiler.compile(lines2, width, this.#theme),
11153
11203
  cursor: {
11154
11204
  row: transcriptLines.length + cursor.row,
11155
11205
  column: cursor.column
@@ -12641,6 +12691,7 @@ var TerminalUI = class {
12641
12691
  #loop = null;
12642
12692
  #transcript;
12643
12693
  #transcriptView;
12694
+ #lineCompiler = new StyledLineCompiler();
12644
12695
  #showReasoning = true;
12645
12696
  #displayPolicy = new DisplayPolicy({
12646
12697
  audience: "terminal",
@@ -12772,9 +12823,16 @@ var TerminalUI = class {
12772
12823
  this.#sessionId = sessionId;
12773
12824
  this.#loop?.requestRender();
12774
12825
  }
12826
+ setContextUsage(tokens, contextWindow) {
12827
+ this.publishEvent({
12828
+ kind: "ui.context_usage",
12829
+ payload: { context_tokens: tokens, context_window: contextWindow }
12830
+ });
12831
+ }
12775
12832
  replaceTranscript(items) {
12776
12833
  this.#transcript.replace(items);
12777
12834
  this.#transcriptView.invalidate();
12835
+ this.#lineCompiler.invalidate();
12778
12836
  this.#loop?.requestRender();
12779
12837
  }
12780
12838
  setComposerText(text) {
@@ -12900,7 +12958,7 @@ var TerminalUI = class {
12900
12958
  #buildHistoryFrameParts(width) {
12901
12959
  const rendered = this.#transcriptView.renderWithMetadata({ width, theme: this.theme });
12902
12960
  return {
12903
- lines: compileStyledLines(rendered.lines, Math.max(12, width), this.theme),
12961
+ lines: this.#lineCompiler.compile(rendered.lines, Math.max(12, width), this.theme),
12904
12962
  activeStart: rendered.activeStart
12905
12963
  };
12906
12964
  }
@@ -12931,7 +12989,7 @@ var TerminalUI = class {
12931
12989
  effort: this.effort
12932
12990
  })
12933
12991
  }).renderWithMetadata({ width: contentWidth, theme: this.theme });
12934
- const lines2 = compileStyledLines(rendered.lines.map((line2) => truncateStyledLine(line2, contentWidth, "")), contentWidth, this.theme);
12992
+ const lines2 = this.#lineCompiler.compile(rendered.lines, contentWidth, this.theme);
12935
12993
  return this.#frameBuilder.build({
12936
12994
  ...options,
12937
12995
  compiledMainScreen: {
@@ -16668,6 +16726,15 @@ function resetEmptySessionTranscript(terminalUi) {
16668
16726
  terminalUi?.replaceTranscript([]);
16669
16727
  terminalUi?.showWelcome();
16670
16728
  }
16729
+ function refreshSessionContextUsage(terminalUi, input) {
16730
+ if (terminalUi === null) return;
16731
+ const context = new ContextBuilder().build(input);
16732
+ const estimator = new DefaultTokenEstimator();
16733
+ terminalUi.setContextUsage(
16734
+ estimator.estimateMessages(context.messages) + estimator.estimateTools(input.tools),
16735
+ input.contextWindow
16736
+ );
16737
+ }
16671
16738
  async function main(argv, options = {}) {
16672
16739
  let parsed;
16673
16740
  try {
@@ -17105,6 +17172,16 @@ async function main(argv, options = {}) {
17105
17172
  input: presenterInput,
17106
17173
  secretInput: presenterSecretInput
17107
17174
  }) : new TerminalCommandPresenter(terminalUi);
17175
+ const refreshContextUsage = () => {
17176
+ refreshSessionContextUsage(terminalUi, {
17177
+ entries: sessionController.history?.entries() ?? [],
17178
+ currentProvider: config.provider,
17179
+ currentModel: config.model,
17180
+ tools: toolRegistry.definitions,
17181
+ contextWindow: selectedModel?.contextWindow ?? 0
17182
+ });
17183
+ };
17184
+ refreshContextUsage();
17108
17185
  const refreshSessionView = () => {
17109
17186
  const currentSessionId = sessionController.currentSessionId;
17110
17187
  if (currentSessionId !== null) {
@@ -17126,6 +17203,7 @@ async function main(argv, options = {}) {
17126
17203
  agent.messages = [system];
17127
17204
  }
17128
17205
  resetEmptySessionTranscript(terminalUi);
17206
+ refreshContextUsage();
17129
17207
  return;
17130
17208
  }
17131
17209
  agent.messages = [...new ContextBuilder().build({
@@ -17134,6 +17212,7 @@ async function main(argv, options = {}) {
17134
17212
  currentModel: config.model
17135
17213
  }).messages];
17136
17214
  terminalUi?.replaceTranscript(projectTranscript(entries));
17215
+ refreshContextUsage();
17137
17216
  };
17138
17217
  const commands = new SessionCommands({
17139
17218
  agent,
@@ -17173,6 +17252,7 @@ async function main(argv, options = {}) {
17173
17252
  terminalUi.state.model = selection.config.model;
17174
17253
  terminalUi.setRuntimeCapabilities({ reasoning: model?.reasoning ?? false });
17175
17254
  }
17255
+ refreshContextUsage();
17176
17256
  }
17177
17257
  });
17178
17258
  const unsubscribers = [];
@@ -17180,6 +17260,9 @@ async function main(argv, options = {}) {
17180
17260
  unsubscribers.push(
17181
17261
  runtime.eventBus.subscribe((event) => {
17182
17262
  sessionSink.publishEvent(projector.project(event, "terminal"));
17263
+ if (event.session_id === runtime.sessionId && (event.kind === "task.completed" || event.kind === "task.cancelled" || event.kind === "task.failed")) {
17264
+ refreshContextUsage();
17265
+ }
17183
17266
  })
17184
17267
  );
17185
17268
  if (terminalUi !== null) {