mirror of
https://github.com/block/buzz.git
synced 2026-11-11 20:01:09 +00:00
fix(desktop): picking Auto on shared compute no longer blocks Create
Selecting "Auto" for a Buzz shared compute agent left Create/Save greyed out. The submit gate requires a non-empty model on the raw provider/model pair (`agentAiConfigurationModeSatisfied`), and the model dropdown wrote blank for Auto. Blank was also wrong on its own terms. Blank means *inherit the global model*, so an agent whose model came out blank ran shared compute against whatever unrelated model global config named - a Claude id sent to a local mesh node. And `"auto"` is a trigger token rather than a final choice: Buzz sets BUZZ_AGENT_PREFER_MESH_FOR_AUTO for this provider, and buzz-agent rewrites exactly `"auto"` to mesh-llm's virtual `mesh` model once the catalog advertises it (which mesh-llm does automatically at two or more models). Any concrete model id defeats that translation, so the old blank encoding could never reach Mixture-of-Agents. The rest of the feature already encoded shared-compute Auto as `"auto"` (provider selection, the global model control); the model dropdown handler was the one inconsistent path. Ordinary providers keep blank, which is the generic inheritance encoding everywhere else - existing blanks are not migrated. `isRelayMesh` is an optional parameter, defaulting to the selection's own provider. The dialogs derive the value already and would ideally pass it for the inherited-provider case, but both files sit at the size ceiling and may not grow; the parameter is there for whoever splits them. The existing test enshrined unconditional Auto -> blank. It is now split by provider, with coverage for the inference fallback and the explicit flag. Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -37,6 +37,19 @@ export const ADVANCED_FIELDS_MOTION_TRANSITION = {
|
||||
} as const;
|
||||
|
||||
export const AUTO_MODEL_DROPDOWN_VALUE = "__auto_model__";
|
||||
/**
|
||||
* The model value that means "Auto" on Buzz shared compute.
|
||||
*
|
||||
* Not blank: blank means *inherit the global model*, which for this provider
|
||||
* resolves to whatever unrelated model the global config names. It is also a
|
||||
* trigger token rather than a final choice -- buzz-agent rewrites exactly
|
||||
* `"auto"` to the virtual `mesh` model when the live catalog offers enough
|
||||
* models for Mixture-of-Agents (`BUZZ_AGENT_PREFER_MESH_FOR_AUTO`), so any
|
||||
* concrete model id defeats that translation.
|
||||
*/
|
||||
export const RELAY_MESH_AUTO_MODEL = "auto";
|
||||
/** Provider id for Buzz shared compute. */
|
||||
export const RELAY_MESH_PROVIDER = "relay-mesh";
|
||||
export const CUSTOM_MODEL_DROPDOWN_VALUE = "__custom_model__";
|
||||
export const AUTO_PROVIDER_DROPDOWN_VALUE = "__auto_provider__";
|
||||
export const CUSTOM_PROVIDER_DROPDOWN_VALUE = "__custom_provider__";
|
||||
|
||||
@@ -228,3 +228,94 @@ test("auto-model selection clears the model; concrete selection sets it", () =>
|
||||
);
|
||||
assert.equal(concrete.model, "gpt-5");
|
||||
});
|
||||
|
||||
test('shared compute encodes Auto as "auto", not blank', () => {
|
||||
// Blank means *inherit the global model*, so on shared compute a blank Auto
|
||||
// resolves to whatever unrelated model the global config names -- a Claude id
|
||||
// sent to a local mesh node -- and it fails the raw-pair submit gate in
|
||||
// `agentAiConfigurationModeSatisfied`, which is what greyed out Create.
|
||||
//
|
||||
// "auto" is also a trigger token, not a final choice: buzz-agent rewrites
|
||||
// exactly "auto" to the virtual `mesh` model when the catalog offers enough
|
||||
// models for Mixture-of-Agents. Any concrete id defeats that translation.
|
||||
const auto = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "relay-mesh", model: "some-physical-model" },
|
||||
{
|
||||
nextValue: "__auto_model__",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
isRelayMesh: true,
|
||||
},
|
||||
);
|
||||
assert.equal(auto.model, "auto");
|
||||
assert.equal(auto.isCustomModelEditing, false);
|
||||
});
|
||||
|
||||
test("a concrete shared-compute model is still honoured verbatim", () => {
|
||||
// Only Auto is special. An explicit pick must reach mesh-llm unchanged.
|
||||
const concrete = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "relay-mesh", model: "auto" },
|
||||
{
|
||||
nextValue: "unsloth/gemma-4-26B",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
isRelayMesh: true,
|
||||
},
|
||||
);
|
||||
assert.equal(concrete.model, "unsloth/gemma-4-26B");
|
||||
});
|
||||
|
||||
test("ordinary providers keep blank for Auto", () => {
|
||||
// Blank is the generic "inherit the global default" encoding everywhere else,
|
||||
// and must not be migrated to "auto".
|
||||
const auto = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "anthropic", model: "claude-opus-5" },
|
||||
{
|
||||
nextValue: "__auto_model__",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
isRelayMesh: false,
|
||||
},
|
||||
);
|
||||
assert.equal(auto.model, "");
|
||||
// Omitting the flag entirely behaves the same, so existing callers are safe.
|
||||
const omitted = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "anthropic", model: "claude-opus-5" },
|
||||
{
|
||||
nextValue: "__auto_model__",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
},
|
||||
);
|
||||
assert.equal(omitted.model, "");
|
||||
});
|
||||
|
||||
test("shared compute is inferred from the selection when the caller omits it", () => {
|
||||
// The Edit dialog cannot pass the flag (its file is at the size ceiling), so
|
||||
// the selection's own provider has to be enough for the common case where the
|
||||
// agent names relay-mesh itself.
|
||||
const auto = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "relay-mesh", model: "some-physical-model" },
|
||||
{
|
||||
nextValue: "__auto_model__",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
},
|
||||
);
|
||||
assert.equal(auto.model, "auto");
|
||||
});
|
||||
|
||||
test("an explicit flag wins over the selection's own provider", () => {
|
||||
// Inheritance case: the picker can run off an effective provider the selection
|
||||
// does not name, so a caller that knows better must be able to say so.
|
||||
const inherited = selectionOnModelDropdownChange(
|
||||
{ ...base, provider: "", model: "x" },
|
||||
{
|
||||
nextValue: "__auto_model__",
|
||||
clearKnownModelOnCustomEntry: false,
|
||||
isModelCustom: false,
|
||||
isRelayMesh: true,
|
||||
},
|
||||
);
|
||||
assert.equal(inherited.model, "auto");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { EnvVarsValue } from "./EnvVarsEditor";
|
||||
import {
|
||||
AUTO_MODEL_DROPDOWN_VALUE,
|
||||
RELAY_MESH_AUTO_MODEL,
|
||||
RELAY_MESH_PROVIDER,
|
||||
AUTO_PROVIDER_DROPDOWN_VALUE,
|
||||
CUSTOM_MODEL_DROPDOWN_VALUE,
|
||||
CUSTOM_PROVIDER_DROPDOWN_VALUE,
|
||||
@@ -147,6 +149,27 @@ export function selectionOnModelDropdownChange(
|
||||
clearKnownModelOnCustomEntry: boolean;
|
||||
/** Caller-computed: whether the current model is outside the known options. */
|
||||
isModelCustom: boolean;
|
||||
/**
|
||||
* Caller-computed: whether the effective provider is Buzz shared compute.
|
||||
*
|
||||
* Shared compute encodes Auto as the literal `"auto"`, not as blank. Blank
|
||||
* means *inherit the global model*, so a blank Auto on shared compute both
|
||||
* resolves to whatever unrelated model the global config names (e.g. a
|
||||
* Claude id sent to a local mesh node) and fails the raw-pair submit gate in
|
||||
* `agentAiConfigurationModeSatisfied`, which requires a non-empty model.
|
||||
*
|
||||
* `"auto"` is also a trigger token rather than a final choice: Buzz sets
|
||||
* `BUZZ_AGENT_PREFER_MESH_FOR_AUTO` for this provider, and buzz-agent
|
||||
* rewrites exactly `"auto"` to the virtual `mesh` model when the live
|
||||
* catalog offers enough models for Mixture-of-Agents. Any concrete model id
|
||||
* defeats that translation.
|
||||
*
|
||||
* Optional. Callers that already derive it (because the picker can be driven
|
||||
* by an *inherited* effective provider, not just the selection's own) should
|
||||
* pass it; otherwise the selection's own provider is used, which covers every
|
||||
* case where the agent names the provider itself.
|
||||
*/
|
||||
isRelayMesh?: boolean;
|
||||
},
|
||||
): RuntimeModelProviderSelection {
|
||||
const next = { ...current };
|
||||
@@ -159,8 +182,15 @@ export function selectionOnModelDropdownChange(
|
||||
return next;
|
||||
}
|
||||
|
||||
const isRelayMesh =
|
||||
params.isRelayMesh ?? current.provider.trim() === RELAY_MESH_PROVIDER;
|
||||
|
||||
next.isCustomModelEditing = false;
|
||||
next.model =
|
||||
params.nextValue === AUTO_MODEL_DROPDOWN_VALUE ? "" : params.nextValue;
|
||||
params.nextValue === AUTO_MODEL_DROPDOWN_VALUE
|
||||
? isRelayMesh
|
||||
? RELAY_MESH_AUTO_MODEL
|
||||
: ""
|
||||
: params.nextValue;
|
||||
return next;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user