fix(beehive): remove redundant global Add provider command

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-21 14:12:54 -04:00
parent e40789ac93
commit 85d287dcb0
2 changed files with 41 additions and 17 deletions

View File

@@ -57,10 +57,9 @@ export class ProviderPanel {
}
this.unsubscribe = client.subscribe(snapshot => { if (this.snapshot.message !== snapshot.message) this.detailOffset = 0; this.snapshot = snapshot; this.dialog?.updateSecretLength(snapshot.secretLength); this.paint(); });
}
private items() { return [...providerInventory(this.snapshot).map(row => row.id), 'add', 'reload']; }
private items() { return [...providerInventory(this.snapshot).map(row => row.id), 'reload']; }
private row() { return providerInventory(this.snapshot).find(row => row.id === this.selected); }
private commands() {
if (this.selected === 'add') return ['Add provider'];
if (this.selected === 'reload') return ['Reload providers'];
if (this.selected.startsWith('type:')) return [this.row()?.type === 'databricks_v2' ? 'Set up Databricks' : `Add ${labels[this.row()!.type]} account`];
return ['Edit provider', 'Test provider', 'Load models', ...(this.row()?.type === 'databricks_v2' ? ['Sign in to Databricks'] : []), 'Set up Codex', 'Set up Pi'];
@@ -105,14 +104,8 @@ export class ProviderPanel {
if (this.dialog === dialog) this.dialog = undefined;
this.repaint(); return result;
}
private async edit(row?: ProviderRow, preset?: ProviderRow['type']) {
private async edit(row: ProviderRow | undefined, type: ProviderRow['type']) {
const generation = this.generation, revision = this.snapshot.revision;
let type = row?.type ?? preset;
if (!type) {
const result = await this.form('ADD PROVIDER', [{ label: 'Type', value: 'openai', choices: types }], 'Continue');
if (!result || generation !== this.generation) return;
type = result.Type as ProviderRow['type'];
}
if (type === 'databricks_v2' && !this.snapshot.databricksHost) {
await this.form('DATABRICKS_HOST REQUIRED', [], 'Close', 'Set DATABRICKS_HOST to your workspace HTTPS origin, for example:\nhttps://your-workspace.cloud.databricks.com\nThen restart Beehive from that environment.\nNo changes have been saved.');
return;
@@ -125,7 +118,7 @@ export class ProviderPanel {
}
const result = await this.form(row ? 'EDIT PROVIDER' : 'ADD PROVIDER', fields);
if (!result || generation !== this.generation) return;
await this.client.request({ action: 'save', provider: row?.id ?? (preset ? `type:${type}` : undefined), revision, values: { type, name: result.Name!, endpoint: result.Endpoint ?? '', wire: result.Wire ?? 'auto' } });
await this.client.request({ action: 'save', provider: row?.id ?? `type:${type}`, revision, values: { type, name: result.Name!, endpoint: result.Endpoint ?? '', wire: result.Wire ?? 'auto' } });
}
private async setup(row: ProviderRow, harness: string) {
const generation = this.generation;
@@ -157,11 +150,10 @@ export class ProviderPanel {
private async run() {
if (this.snapshot.phase === 'busy' || this.dialog || !this.active) return;
const command = this.commands()[this.actionIndex], row = this.row();
if (command === 'Add provider') await this.edit();
else if (command === 'Reload providers') await this.client.request({ action: 'reload' });
if (command === 'Reload providers') await this.client.request({ action: 'reload' });
else if (row) {
if (row.id.startsWith('type:')) { await this.edit(undefined, row.type); return; }
if (command === 'Edit provider') await this.edit(row);
if (command === 'Edit provider') await this.edit(row, row.type);
else if (command === 'Set up Codex' || command === 'Set up Pi') await this.setup(row, command === 'Set up Codex' ? 'codex' : 'pi');
else await this.client.request({ action: command === 'Test provider' ? 'test' : command === 'Load models' ? 'models' : 'login', provider: row.id, revision: this.snapshot.revision });
}
@@ -184,15 +176,15 @@ export class ProviderPanel {
const available = Math.max(1, leftWidth - status.length - 3);
const name = row.name.length > available ? row.name.slice(0, Math.max(1, available - 1)) + '…' : row.name;
renderable.content = `◇ ${name.padEnd(available)} ${status}`;
} else renderable.content = (id === 'add' ? '+ Add provider' : '↻ Reload providers').slice(0, leftWidth);
} else renderable.content = '↻ Reload providers'.slice(0, leftWidth);
renderable.bg = id === this.selected ? palette.selected : palette.surface; renderable.fg = id === this.selected ? palette.selectedText : palette.text;
});
const row = this.row();
this.detailTitle.content = row ? row.name.toUpperCase() : this.selected === 'add' ? 'ADD PROVIDER' : 'RELOAD PROVIDERS'; this.detailTitle.width = width;
this.detailTitle.content = row ? row.name.toUpperCase() : 'RELOAD PROVIDERS'; this.detailTitle.width = width;
const commands = this.commands();
const compact = this.renderer.height < 28;
const ownsResult = this.snapshot.resultTarget === this.selected;
const info = row ? [`State ${row.state}`, `Type ${row.type}`, ...(row.endpoint ? [`Connects to ${row.endpoint}`] : []), '', row.detail] : this.selected === 'add' ? ['Choose OpenAI, Anthropic, OpenAI-compatible, OpenRouter, or Databricks v2.', '', 'Credentials stay in the OS credential store.'] : ['Reload saved providers and the DATABRICKS_HOST workspace. No owner sign-in is required.'];
const info = row ? [`State ${row.state}`, `Type ${row.type}`, ...(row.endpoint ? [`Connects to ${row.endpoint}`] : []), '', row.detail] : ['Reload saved providers and the DATABRICKS_HOST workspace. No owner sign-in is required.'];
const wrap = (lines: string[]) => lines.flatMap(line => { const result: string[] = []; for (let start = 0; start < Math.max(1, line.length); start += width) result.push(line.slice(start, start + width)); return result; });
const fullDetails = wrap(info);
// Reserve every action and a result viewport before allocating detail rows.

View File

@@ -27,7 +27,7 @@ for (const [width, height] of [[120, 40], [60, 20]]) test(`Providers signed-out
try {
for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right');
ui.mockInput.pressEnter(); await ui.renderOnce();
assert.match(ui.captureCharFrame(), /Add provider/);
assert.doesNotMatch(ui.captureCharFrame(), /Add provider/);
ui.mockInput.pressTab(); await ui.renderOnce();
assert.equal(shell.state.focus, 'detail');
assert.match(ui.captureCharFrame(), /Test provider/);
@@ -220,3 +220,35 @@ for (const [width, height] of [[120, 40], [60, 20]]) test(`all provider actions
assert.ok(recoveryVisible);
} finally { shell.close(); }
});
for (const [width, height] of [[120, 40], [60, 20]]) test(`provider inventory has no global Add command and retains saved accounts at ${width}x${height}`, async () => {
const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture();
f.snapshot.rows.push({ ...f.snapshot.rows[0]!, id: 'second', name: 'Second' });
const before = structuredClone(f.snapshot);
const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client);
try {
for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right');
ui.mockInput.pressEnter(); await ui.renderOnce();
assert.doesNotMatch(ui.captureCharFrame(), /Add provider/);
assert.match(ui.captureCharFrame(), /Fixture/);
assert.match(ui.captureCharFrame(), /Second/);
ui.mockInput.pressArrow('down'); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce();
assert.match(ui.captureCharFrame(), /EDIT PROVIDER/);
assert.match(ui.captureCharFrame(), /Second/);
ui.mockInput.pressEscape(); await new Promise(resolve => setTimeout(resolve, 50)); await ui.renderOnce();
assert.doesNotMatch(ui.captureCharFrame(), /EDIT PROVIDER/);
ui.mockInput.pressTab();
for (let i = 0; i < 5; i++) ui.mockInput.pressArrow('down');
ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce();
assert.deepEqual(f.requests.at(-1), { action: 'reload' });
assert.doesNotMatch(ui.captureCharFrame(), /Add provider|ADD PROVIDER/);
const lines = ui.captureCharFrame().split('\n');
const y = lines.findIndex(line => line.includes('› Reload providers'));
assert.ok(y >= 0, ui.captureCharFrame());
const count = f.requests.length;
await ui.mockMouse.click(lines[y]!.indexOf('Reload providers'), y); await ui.renderOnce();
assert.equal(f.requests.length, count + 1);
assert.deepEqual(f.requests.at(-1), { action: 'reload' });
assert.deepEqual(f.snapshot, before);
} finally { shell.close(); }
});