mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 16:04:43 +00:00
fix(app): a draft keeps sending an option no control has drawn
The previous change stopped sending any edit the latest answer drew no control for, which also dropped options set without a control (a plugin setting a token budget on the draft). Only an edit of a key an earlier answer for this draft drew as a control stops applying when a later answer draws none; every other edit always applies.
This commit is contained in:
@@ -107,14 +107,27 @@ describe("draft options model", () => {
|
||||
|
||||
expect(ownDraftOptions(model.getState())).toEqual({ sandbox_mode: "read-only" });
|
||||
|
||||
// Back on the first model, the toggle is still on.
|
||||
model.applyResolved(answer("claude", "opus-again", { fast_mode: true }, [FAST_MODE]));
|
||||
// Back on a model that draws the control, the toggle applies again. The daemon's answer
|
||||
// for the request without it said Fast was off; the request that carries it follows.
|
||||
model.applyResolved(answer("claude", "opus-again", { fast_mode: false }, [FAST_MODE]));
|
||||
expect(ownDraftOptions(model.getState())).toEqual({
|
||||
sandbox_mode: "read-only",
|
||||
fast_mode: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("always sends an option no control has drawn", () => {
|
||||
const FAST_MODE: AgentOptionControl = { type: "toggle", key: "fast_mode", label: "Fast" };
|
||||
const model = openDraftOptions({ provider: "claude" });
|
||||
model.applyResolved(answer("claude", "k1", {}, [FAST_MODE]));
|
||||
|
||||
model.setOption("token_budget", 5);
|
||||
|
||||
expect(ownDraftOptions(model.getState())).toEqual({ token_budget: 5 });
|
||||
model.applyResolved(answer("claude", "k2", {}, []));
|
||||
expect(ownDraftOptions(model.getState())).toEqual({ token_budget: 5 });
|
||||
});
|
||||
|
||||
it("keeps a draft's seed when the first provider is chosen", () => {
|
||||
const model = openDraftOptions({ provider: null, seed: { web_search: true } });
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface DraftOptionsState {
|
||||
remembered: ProviderOptions;
|
||||
edits: ProviderOptions;
|
||||
resolved: ResolvedDraftOptions | null;
|
||||
/** Every key an answer for this provider has drawn as a control. */
|
||||
drawn: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export interface DraftOptionsModel {
|
||||
@@ -54,6 +56,7 @@ export function openDraftOptions(input: {
|
||||
remembered: {},
|
||||
edits: {},
|
||||
resolved: null,
|
||||
drawn: new Set(),
|
||||
};
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
@@ -77,7 +80,14 @@ export function openDraftOptions(input: {
|
||||
if (provider === current.provider) return current;
|
||||
// The first provider a draft picks does not discard what it was opened with.
|
||||
if (current.provider === null) return { ...current, provider };
|
||||
return { ...current, provider, seed: {}, edits: {}, resolved: null };
|
||||
return {
|
||||
...current,
|
||||
provider,
|
||||
seed: {},
|
||||
edits: {},
|
||||
resolved: null,
|
||||
drawn: new Set(),
|
||||
};
|
||||
}),
|
||||
applyProfile: (profile) =>
|
||||
publish((current) => ({
|
||||
@@ -86,6 +96,7 @@ export function openDraftOptions(input: {
|
||||
seed: profile.options,
|
||||
edits: {},
|
||||
resolved: current.provider === profile.provider ? current.resolved : null,
|
||||
drawn: current.provider === profile.provider ? current.drawn : new Set(),
|
||||
})),
|
||||
setRemembered: (remembered) => publish((current) => ({ ...current, remembered })),
|
||||
setOption: (key, value) =>
|
||||
@@ -94,15 +105,22 @@ export function openDraftOptions(input: {
|
||||
? current
|
||||
: { ...current, edits: { ...current.edits, [key]: value } },
|
||||
),
|
||||
applyResolved: (resolved) => publish((current) => ({ ...current, resolved })),
|
||||
applyResolved: (resolved) =>
|
||||
publish((current) => ({
|
||||
...current,
|
||||
resolved,
|
||||
drawn: new Set([...current.drawn, ...resolved.controls.map((control) => control.key)]),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything the draft would create with, for the daemon to resolve. A last-used value applies
|
||||
* once the provider has told us it still draws a control for that key, and never over the seed.
|
||||
* A toggle made in this draft stops applying when the latest answer draws no control for it (the
|
||||
* model changed to one without that control) and applies again when a control for it returns.
|
||||
* An edit of a key an earlier answer drew as a control stops applying when the latest answer
|
||||
* draws none for it (the model changed to one without that control) and applies again when a
|
||||
* control for it returns. An edit of a key no answer has drawn is an option without a control
|
||||
* and always applies.
|
||||
*/
|
||||
export function ownDraftOptions(state: DraftOptionsState): ProviderOptions | undefined {
|
||||
const controlKeys = new Set((state.resolved?.controls ?? []).map((control) => control.key));
|
||||
@@ -112,7 +130,7 @@ export function ownDraftOptions(state: DraftOptionsState): ProviderOptions | und
|
||||
),
|
||||
);
|
||||
const edits = Object.fromEntries(
|
||||
Object.entries(state.edits).filter(([key]) => !state.resolved || controlKeys.has(key)),
|
||||
Object.entries(state.edits).filter(([key]) => !state.drawn.has(key) || controlKeys.has(key)),
|
||||
);
|
||||
const own = { ...state.seed, ...remembered, ...edits };
|
||||
return Object.keys(own).length > 0 ? own : undefined;
|
||||
|
||||
Reference in New Issue
Block a user