laohuang 0.8.1 → 0.8.3

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/README.md CHANGED
@@ -29,8 +29,15 @@ Google Vertex, OAuth-only providers, and OpenAI Codex. Use `/providers` to see
29
29
  available, configured, and verified status independently.
30
30
 
31
31
  `/login <provider>` stores API-key credentials, `/logout <provider>` removes
32
- them, `/model <provider> <model>` switches the session, and `/apikey` remains a
33
- compatibility alias. `verified` means an explicitly authorized live native
32
+ them, and `/model` opens a searchable list of all available models from
33
+ configured providers, including credentials supplied through environment variables.
34
+ Search by provider ID, model ID, or model name; scroll to browse the full list.
35
+ If no models are available, the CLI points to `/login`.
36
+ `/model <provider>` narrows the list, `/model <provider> <model>` switches
37
+ directly, and `/apikey` remains a compatibility alias. Switching models affects
38
+ only the current session, not the default profile.
39
+
40
+ `verified` means an explicitly authorized live native
34
41
  tool-call/tool-result E2E was recorded; no providers are checked in as verified
35
42
  by default.
36
43
 
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) {
@@ -9985,7 +10022,7 @@ function renderMarkdownStyledLines(text, width) {
9985
10022
  const layoutWidth = Math.max(12, width);
9986
10023
  const lines2 = [];
9987
10024
  let previous = null;
9988
- for (const block of renderBlocks(clean, layoutWidth)) {
10025
+ for (const block of renderBlocks(clean, layoutWidth, Math.max(1, width))) {
9989
10026
  const leadingBlank = previous === null ? block.kind === "list" || block.kind === "table" || block.kind === "quote" : previous !== "hr";
9990
10027
  if (leadingBlank) {
9991
10028
  lines2.push(line());
@@ -10009,7 +10046,7 @@ function styleKey(style) {
10009
10046
  style.background ?? ""
10010
10047
  ].join("|");
10011
10048
  }
10012
- function renderBlocks(text, layoutWidth) {
10049
+ function renderBlocks(text, layoutWidth, tableWidth) {
10013
10050
  const lines2 = text.replace(/\r\n/g, "\n").split("\n");
10014
10051
  const blocks = [];
10015
10052
  let paragraph = [];
@@ -10057,6 +10094,7 @@ function renderBlocks(text, layoutWidth) {
10057
10094
  const delimiter = splitTableRow(lines2[index + 1].trim());
10058
10095
  if (header !== null && delimiter !== null && delimiter.length === header.length && delimiter.every((cell) => /^:?-+:?$/.test(cell))) {
10059
10096
  flushParagraph();
10097
+ const tableStart = index;
10060
10098
  index += 2;
10061
10099
  const rows = [];
10062
10100
  while (index < lines2.length) {
@@ -10073,7 +10111,7 @@ function renderBlocks(text, layoutWidth) {
10073
10111
  }
10074
10112
  blocks.push({
10075
10113
  kind: "table",
10076
- lines: renderTable(header, rows, layoutWidth)
10114
+ lines: renderTable(header, rows, tableWidth, lines2.slice(tableStart, index))
10077
10115
  });
10078
10116
  continue;
10079
10117
  }
@@ -10212,7 +10250,7 @@ function segmentsWidth(segments) {
10212
10250
  }
10213
10251
  return width;
10214
10252
  }
10215
- function renderTable(header, rows, layoutWidth) {
10253
+ function renderTable(header, rows, layoutWidth, rawLines) {
10216
10254
  const columnCount = header.length;
10217
10255
  const borderStyle = {
10218
10256
  foreground: "border_muted"
@@ -10224,12 +10262,26 @@ function renderTable(header, rows, layoutWidth) {
10224
10262
  const normalize = (cells) => Array.from({ length: columnCount }, (_, i) => cells[i] ?? "");
10225
10263
  const headerCells = normalize(header).map((cell) => parseInline(cell, headerStyle));
10226
10264
  const bodyCells = rows.map((row) => normalize(row).map((cell) => parseInline(cell, {})));
10265
+ const minWidths = Array(columnCount).fill(1);
10266
+ for (const row of [headerCells, ...bodyCells]) {
10267
+ for (let i = 0; i < columnCount; i += 1) {
10268
+ for (const segment of row[i]) {
10269
+ for (const cluster of graphemeClusters(segment.text)) {
10270
+ minWidths[i] = Math.max(minWidths[i], clusterWidth(cluster));
10271
+ }
10272
+ }
10273
+ }
10274
+ }
10275
+ const spacingWidth = 2 * columnCount;
10276
+ if (minWidths.reduce((total, width) => total + width, spacingWidth) > layoutWidth) {
10277
+ return rawLines.flatMap((text) => wrapSegments([{ text, style: {} }], layoutWidth));
10278
+ }
10227
10279
  const widths = Array.from({ length: columnCount }, (_, i) => Math.max(1, segmentsWidth(headerCells[i]), ...bodyCells.map((row) => segmentsWidth(row[i]))));
10228
10280
  const totalWidth = () => widths.reduce((total, w) => total + w, 0) + 2 * (columnCount - 1) + 2;
10229
- while (totalWidth() > layoutWidth && Math.max(...widths) > 1) {
10230
- let widest = 0;
10231
- for (let i = 1; i < widths.length; i += 1) {
10232
- if (widths[i] > widths[widest]) {
10281
+ while (totalWidth() > layoutWidth) {
10282
+ let widest = -1;
10283
+ for (let i = 0; i < widths.length; i += 1) {
10284
+ if (widths[i] > minWidths[i] && (widest === -1 || widths[i] > widths[widest])) {
10233
10285
  widest = i;
10234
10286
  }
10235
10287
  }
@@ -10259,7 +10311,7 @@ function renderTableRows(cells, widths, borderStyle, padStyle) {
10259
10311
  if (i > 0) {
10260
10312
  line2.push({ style: borderStyle, text: " " });
10261
10313
  }
10262
- const cellLine = wrappedCells[i][row];
10314
+ const cellLine = wrappedCells[i][row] ?? [];
10263
10315
  line2.push(...cellLine);
10264
10316
  const pad = widths[i] - segmentsWidth(cellLine);
10265
10317
  if (pad > 0) {
@@ -10933,10 +10985,10 @@ var Transcript = class {
10933
10985
  const key = cacheKey(block);
10934
10986
  const signature = blockSignature(block);
10935
10987
  const cached = this.#cache.get(key);
10936
- if (cached !== void 0 && cached.width === context.width && cached.signature === signature) {
10988
+ if (cached !== void 0 && cached.block === block && cached.width === context.width && cached.signature === signature) {
10937
10989
  return cached.lines;
10938
10990
  }
10939
- if (cached !== void 0 && cached.signature === signature) {
10991
+ if (cached !== void 0 && cached.block === block && cached.signature === signature) {
10940
10992
  const lines3 = cached.component.render(context).lines;
10941
10993
  this.#cache.set(key, {
10942
10994
  ...cached,
@@ -10948,6 +11000,7 @@ var Transcript = class {
10948
11000
  const component = this.#createBlockComponent(block);
10949
11001
  const lines2 = component.render(context).lines;
10950
11002
  this.#cache.set(key, {
11003
+ block,
10951
11004
  component,
10952
11005
  width: context.width,
10953
11006
  signature,
@@ -11096,6 +11149,11 @@ var FrameBuilder = class {
11096
11149
  #effort;
11097
11150
  #title;
11098
11151
  #theme;
11152
+ #transcriptView;
11153
+ #lineCompiler = new StyledLineCompiler();
11154
+ #previousWidth = 0;
11155
+ #previousSources = [];
11156
+ #previousLines = [];
11099
11157
  constructor(options) {
11100
11158
  this.#state = options.state;
11101
11159
  this.#transcript = options.transcript;
@@ -11105,12 +11163,21 @@ var FrameBuilder = class {
11105
11163
  this.#effort = options.effort ?? null;
11106
11164
  this.#title = options.title ?? "laoHuang";
11107
11165
  this.#theme = options.theme ?? resolveTerminalTheme();
11166
+ this.#transcriptView = new Transcript({ blocks: this.#transcript.blocks() });
11108
11167
  }
11109
11168
  build(options) {
11110
11169
  const terminalWidth = Math.max(1, options.width);
11111
11170
  const width = Math.max(1, terminalWidth - 1);
11112
11171
  const mainScreen = options.compiledMainScreen ?? this.#fallbackMainScreen(options, width);
11113
- const lines2 = mainScreen.lines.map((value) => visibleWidth(value) <= width ? value : truncateToWidth(value, width));
11172
+ const lines2 = mainScreen.lines.map((value, index) => {
11173
+ if (this.#previousWidth === width && this.#previousSources[index] === value) {
11174
+ return this.#previousLines[index];
11175
+ }
11176
+ return visibleWidth(value) <= width ? value : truncateToWidth(value, width);
11177
+ });
11178
+ this.#previousWidth = width;
11179
+ this.#previousSources = [...mainScreen.lines];
11180
+ this.#previousLines = lines2;
11114
11181
  const cursor = {
11115
11182
  row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines2.length - 1))),
11116
11183
  col: Math.max(0, Math.min(mainScreen.cursor.column, width - 1))
@@ -11132,9 +11199,7 @@ var FrameBuilder = class {
11132
11199
  return this.#status().render({ width: Math.max(1, width), theme: this.#theme }).lines.map(lineText).join("\n");
11133
11200
  }
11134
11201
  #fallbackMainScreen(options, width) {
11135
- const transcript = new Transcript({
11136
- blocks: this.#transcript.blocks()
11137
- }).renderWithMetadata({ width, theme: this.#theme });
11202
+ const transcript = this.#transcriptView.renderWithMetadata({ width, theme: this.#theme });
11138
11203
  const transcriptLines = transcript.lines;
11139
11204
  const composer = new Composer({
11140
11205
  editor: options.editor,
@@ -11149,7 +11214,7 @@ var FrameBuilder = class {
11149
11214
  const cursor = composer.cursor ?? { row: Math.max(0, composer.lines.length - 1), column: 0 };
11150
11215
  const lines2 = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
11151
11216
  return {
11152
- lines: compileStyledLines(lines2, width, this.#theme),
11217
+ lines: this.#lineCompiler.compile(lines2, width, this.#theme),
11153
11218
  cursor: {
11154
11219
  row: transcriptLines.length + cursor.row,
11155
11220
  column: cursor.column
@@ -12641,6 +12706,7 @@ var TerminalUI = class {
12641
12706
  #loop = null;
12642
12707
  #transcript;
12643
12708
  #transcriptView;
12709
+ #lineCompiler = new StyledLineCompiler();
12644
12710
  #showReasoning = true;
12645
12711
  #displayPolicy = new DisplayPolicy({
12646
12712
  audience: "terminal",
@@ -12772,9 +12838,16 @@ var TerminalUI = class {
12772
12838
  this.#sessionId = sessionId;
12773
12839
  this.#loop?.requestRender();
12774
12840
  }
12841
+ setContextUsage(tokens, contextWindow) {
12842
+ this.publishEvent({
12843
+ kind: "ui.context_usage",
12844
+ payload: { context_tokens: tokens, context_window: contextWindow }
12845
+ });
12846
+ }
12775
12847
  replaceTranscript(items) {
12776
12848
  this.#transcript.replace(items);
12777
12849
  this.#transcriptView.invalidate();
12850
+ this.#lineCompiler.invalidate();
12778
12851
  this.#loop?.requestRender();
12779
12852
  }
12780
12853
  setComposerText(text) {
@@ -12900,7 +12973,7 @@ var TerminalUI = class {
12900
12973
  #buildHistoryFrameParts(width) {
12901
12974
  const rendered = this.#transcriptView.renderWithMetadata({ width, theme: this.theme });
12902
12975
  return {
12903
- lines: compileStyledLines(rendered.lines, Math.max(12, width), this.theme),
12976
+ lines: this.#lineCompiler.compile(rendered.lines, Math.max(12, width), this.theme),
12904
12977
  activeStart: rendered.activeStart
12905
12978
  };
12906
12979
  }
@@ -12931,7 +13004,7 @@ var TerminalUI = class {
12931
13004
  effort: this.effort
12932
13005
  })
12933
13006
  }).renderWithMetadata({ width: contentWidth, theme: this.theme });
12934
- const lines2 = compileStyledLines(rendered.lines.map((line2) => truncateStyledLine(line2, contentWidth, "")), contentWidth, this.theme);
13007
+ const lines2 = this.#lineCompiler.compile(rendered.lines, contentWidth, this.theme);
12935
13008
  return this.#frameBuilder.build({
12936
13009
  ...options,
12937
13010
  compiledMainScreen: {
@@ -14679,7 +14752,7 @@ var SessionCommands = class {
14679
14752
  this.registry = new CommandRegistry([
14680
14753
  {
14681
14754
  name: "/model",
14682
- description: "\u9009\u62E9\u5F53\u524D\u4F9B\u5E94\u5546\u7684\u6A21\u578B\u6216\u5207\u6362\u4F9B\u5E94\u5546",
14755
+ description: "\u641C\u7D22\u5E76\u9009\u62E9\u5DF2\u914D\u7F6E\u4F9B\u5E94\u5546\u7684\u6A21\u578B",
14683
14756
  usage: "/model [provider|model] [model]",
14684
14757
  handler: (args) => this.handleModel(args),
14685
14758
  allowedStates: IDLE_ONLY,
@@ -15066,16 +15139,10 @@ ${sessionDisplayDescription(session, {
15066
15139
  }
15067
15140
  let provider;
15068
15141
  let modelName;
15069
- if (args.length === 0) {
15070
- const selectedProvider = await this.selectModelProvider();
15071
- if (selectedProvider === null) {
15072
- return true;
15073
- }
15074
- provider = selectedProvider;
15075
- } else if (args.length === 2) {
15142
+ if (args.length === 2) {
15076
15143
  provider = args[0];
15077
15144
  modelName = args[1];
15078
- } else {
15145
+ } else if (args.length === 1) {
15079
15146
  const argument = args[0];
15080
15147
  if (this.#catalog.getProvider(argument) !== void 0) {
15081
15148
  provider = argument;
@@ -15086,9 +15153,24 @@ ${sessionDisplayDescription(session, {
15086
15153
  }
15087
15154
  if (modelName === void 0) {
15088
15155
  try {
15089
- const models = await this.#selector.listModels(provider, "");
15156
+ let models;
15157
+ if (provider === void 0) {
15158
+ const available = await this.#selector.listConfiguredModels();
15159
+ models = available.models;
15160
+ for (const failure of available.errors) {
15161
+ this.notice(
15162
+ `Could not list models for ${failure.provider}: ${errorMessage10(failure.error)}`,
15163
+ "warning"
15164
+ );
15165
+ }
15166
+ } else {
15167
+ models = await this.#selector.listModels(provider, "");
15168
+ }
15090
15169
  if (models.length === 0) {
15091
- this.notice(`No models available for provider: ${provider}`, "error");
15170
+ this.notice(
15171
+ provider === void 0 ? "No models available from configured providers. Use /login to configure a provider." : `No models available for provider: ${provider}. Use /login ${provider} to configure credentials.`,
15172
+ "warning"
15173
+ );
15092
15174
  return true;
15093
15175
  }
15094
15176
  if (this.#presenter === null) {
@@ -15097,26 +15179,34 @@ ${sessionDisplayDescription(session, {
15097
15179
  }
15098
15180
  const selected = await this.#presenter.select({
15099
15181
  id: "model-name",
15100
- title: `Select model for ${provider}`,
15101
- items: models.map((model) => ({
15102
- value: `${provider}/${model.id}`,
15103
- label: model.name,
15104
- description: provider
15182
+ title: provider === void 0 ? "Select model from configured providers" : `Select model for ${provider}`,
15183
+ items: models.map((model2) => ({
15184
+ value: `${model2.provider}/${model2.id}`,
15185
+ label: model2.name,
15186
+ description: model2.provider
15105
15187
  })),
15106
- currentValue: this.#currentConfig.provider === provider ? `${provider}/${this.#currentConfig.model}` : void 0,
15188
+ currentValue: `${this.#currentConfig.provider}/${this.#currentConfig.model}`,
15107
15189
  searchable: true,
15108
- maxVisible: 20
15190
+ maxVisible: 10
15109
15191
  });
15110
15192
  if (selected === null) {
15111
15193
  return true;
15112
15194
  }
15113
- const prefix = `${provider}/`;
15114
- modelName = selected.startsWith(prefix) ? selected.slice(prefix.length) : selected;
15195
+ const model = models.find((candidate) => `${candidate.provider}/${candidate.id}` === selected);
15196
+ if (model === void 0) {
15197
+ this.notice("Selected model is no longer available. Run /model to refresh the list.", "error");
15198
+ return true;
15199
+ }
15200
+ provider = model.provider;
15201
+ modelName = model.id;
15115
15202
  } catch (error) {
15116
15203
  this.notice(`Could not list models: ${errorMessage10(error)}`, "error");
15117
15204
  return true;
15118
15205
  }
15119
15206
  }
15207
+ if (provider === void 0) {
15208
+ return true;
15209
+ }
15120
15210
  let selection;
15121
15211
  try {
15122
15212
  selection = await this.#selector.selectExact({
@@ -15129,6 +15219,7 @@ ${sessionDisplayDescription(session, {
15129
15219
  return true;
15130
15220
  }
15131
15221
  if (selection === null) {
15222
+ this.notice(`Use /login ${provider} to configure credentials before switching models.`, "warning");
15132
15223
  return true;
15133
15224
  }
15134
15225
  const previousProvider = this.#currentConfig.provider;
@@ -15387,23 +15478,6 @@ ${sessionDisplayDescription(session, {
15387
15478
  )
15388
15479
  };
15389
15480
  }
15390
- async selectModelProvider() {
15391
- if (this.#presenter === null) {
15392
- this.notice("Model provider selection is unavailable.", "error");
15393
- return null;
15394
- }
15395
- const selected = await this.#presenter.select({
15396
- id: "model-provider",
15397
- title: "Select model provider",
15398
- items: this.#selector.listProviders().map((provider) => ({
15399
- value: provider.id,
15400
- label: provider.name,
15401
- description: provider.id
15402
- })),
15403
- currentValue: this.#currentConfig.provider
15404
- });
15405
- return selected;
15406
- }
15407
15481
  *modelCompletions(args) {
15408
15482
  if (args.length === 0) {
15409
15483
  yield ["current", "\u663E\u793A\u5F53\u524D\u6A21\u578B"];
@@ -15520,13 +15594,31 @@ var ModelSelector = class {
15520
15594
  return this.#catalog.listProviders();
15521
15595
  }
15522
15596
  async listModels(provider, query) {
15597
+ if (!await this.#providerAuth.ensureConfigured(provider, { promptIfMissing: false })) {
15598
+ return [];
15599
+ }
15523
15600
  await this.#catalog.refresh(provider);
15524
15601
  return filterModels(
15525
15602
  await this.#catalog.listAvailableModels(provider),
15526
- query,
15527
- 20
15603
+ query
15528
15604
  );
15529
15605
  }
15606
+ async listConfiguredModels() {
15607
+ const providers = this.#catalog.listProviders();
15608
+ const results = await Promise.allSettled(
15609
+ providers.map((provider) => this.listModels(provider.id, ""))
15610
+ );
15611
+ const models = [];
15612
+ const errors = [];
15613
+ for (const [index, result] of results.entries()) {
15614
+ if (result.status === "fulfilled") {
15615
+ models.push(...result.value);
15616
+ } else {
15617
+ errors.push({ provider: providers[index].id, error: result.reason });
15618
+ }
15619
+ }
15620
+ return { models: filterModels(models, ""), errors };
15621
+ }
15530
15622
  async selectExact(options) {
15531
15623
  const provider = this.#catalog.getProvider(options.providerName);
15532
15624
  if (provider === void 0) {
@@ -15557,7 +15649,7 @@ var ModelSelector = class {
15557
15649
  };
15558
15650
  }
15559
15651
  };
15560
- function filterModels(models, query, limit = 20) {
15652
+ function filterModels(models, query) {
15561
15653
  const normalized = query.toLowerCase().trim();
15562
15654
  const terms = normalized.split(/\s+/).filter((term) => term.length > 0);
15563
15655
  const matched = models.filter((model) => {
@@ -15581,9 +15673,9 @@ function filterModels(models, query, limit = 20) {
15581
15673
  if (leftPrefix !== rightPrefix) {
15582
15674
  return leftPrefix ? -1 : 1;
15583
15675
  }
15584
- return left.id.localeCompare(right.id);
15676
+ return left.provider.localeCompare(right.provider) || left.id.localeCompare(right.id);
15585
15677
  });
15586
- return matched.slice(0, limit);
15678
+ return matched;
15587
15679
  }
15588
15680
 
15589
15681
  // src/plain-command-presenter.ts
@@ -16668,6 +16760,15 @@ function resetEmptySessionTranscript(terminalUi) {
16668
16760
  terminalUi?.replaceTranscript([]);
16669
16761
  terminalUi?.showWelcome();
16670
16762
  }
16763
+ function refreshSessionContextUsage(terminalUi, input) {
16764
+ if (terminalUi === null) return;
16765
+ const context = new ContextBuilder().build(input);
16766
+ const estimator = new DefaultTokenEstimator();
16767
+ terminalUi.setContextUsage(
16768
+ estimator.estimateMessages(context.messages) + estimator.estimateTools(input.tools),
16769
+ input.contextWindow
16770
+ );
16771
+ }
16671
16772
  async function main(argv, options = {}) {
16672
16773
  let parsed;
16673
16774
  try {
@@ -17105,6 +17206,16 @@ async function main(argv, options = {}) {
17105
17206
  input: presenterInput,
17106
17207
  secretInput: presenterSecretInput
17107
17208
  }) : new TerminalCommandPresenter(terminalUi);
17209
+ const refreshContextUsage = () => {
17210
+ refreshSessionContextUsage(terminalUi, {
17211
+ entries: sessionController.history?.entries() ?? [],
17212
+ currentProvider: config.provider,
17213
+ currentModel: config.model,
17214
+ tools: toolRegistry.definitions,
17215
+ contextWindow: selectedModel?.contextWindow ?? 0
17216
+ });
17217
+ };
17218
+ refreshContextUsage();
17108
17219
  const refreshSessionView = () => {
17109
17220
  const currentSessionId = sessionController.currentSessionId;
17110
17221
  if (currentSessionId !== null) {
@@ -17126,6 +17237,7 @@ async function main(argv, options = {}) {
17126
17237
  agent.messages = [system];
17127
17238
  }
17128
17239
  resetEmptySessionTranscript(terminalUi);
17240
+ refreshContextUsage();
17129
17241
  return;
17130
17242
  }
17131
17243
  agent.messages = [...new ContextBuilder().build({
@@ -17134,6 +17246,7 @@ async function main(argv, options = {}) {
17134
17246
  currentModel: config.model
17135
17247
  }).messages];
17136
17248
  terminalUi?.replaceTranscript(projectTranscript(entries));
17249
+ refreshContextUsage();
17137
17250
  };
17138
17251
  const commands = new SessionCommands({
17139
17252
  agent,
@@ -17173,6 +17286,7 @@ async function main(argv, options = {}) {
17173
17286
  terminalUi.state.model = selection.config.model;
17174
17287
  terminalUi.setRuntimeCapabilities({ reasoning: model?.reasoning ?? false });
17175
17288
  }
17289
+ refreshContextUsage();
17176
17290
  }
17177
17291
  });
17178
17292
  const unsubscribers = [];
@@ -17180,6 +17294,9 @@ async function main(argv, options = {}) {
17180
17294
  unsubscribers.push(
17181
17295
  runtime.eventBus.subscribe((event) => {
17182
17296
  sessionSink.publishEvent(projector.project(event, "terminal"));
17297
+ if (event.session_id === runtime.sessionId && (event.kind === "task.completed" || event.kind === "task.cancelled" || event.kind === "task.failed")) {
17298
+ refreshContextUsage();
17299
+ }
17183
17300
  })
17184
17301
  );
17185
17302
  if (terminalUi !== null) {
@@ -17240,6 +17357,10 @@ async function main(argv, options = {}) {
17240
17357
  await sessionRecorder.close();
17241
17358
  await sessionController.close();
17242
17359
  }
17360
+ const renderError = terminalUi?.renderError ?? null;
17361
+ if (renderError !== null) {
17362
+ throw new Error(`Terminal rendering failed: ${errorMessage11(renderError)}`, { cause: renderError });
17363
+ }
17243
17364
  return cleanShutdown ? 0 : 1;
17244
17365
  }
17245
17366
  function defaultSessionsRoot(environ) {