Set resource caps and inference when creating an agent
POST /api/agents takes optional limits and inference objects, checked by the same rules as the two PUT routes before anything is queued and stored ahead of the job graph. The create form gets collapsed sections for both, reusing the fields of the detail-panel editors.
This commit is contained in:
parent
aaac754356
commit
e36a15de91
8 changed files with 389 additions and 100 deletions
|
|
@ -88,6 +88,100 @@ function inferenceUrl(agent: string): string {
|
|||
return `/api/agents/${encodeURIComponent(agent)}/inference`;
|
||||
}
|
||||
|
||||
// The four inference boxes as typed. Shared by the editor here and the
|
||||
// create-agent form, which send what `draftBody` makes of them.
|
||||
export interface InferenceDraft {
|
||||
model: string;
|
||||
effort: string;
|
||||
subagents: string;
|
||||
maxSubagents: string;
|
||||
}
|
||||
|
||||
export const EMPTY_INFERENCE_DRAFT: InferenceDraft = {
|
||||
model: "",
|
||||
effort: "",
|
||||
subagents: "",
|
||||
maxSubagents: "",
|
||||
};
|
||||
|
||||
// Why the draft would be refused, or null when it is fine to send.
|
||||
export function draftProblem(d: InferenceDraft): string | null {
|
||||
return (
|
||||
modelNameProblem(d.model.trim()) ??
|
||||
parseModels(d.subagents)
|
||||
.map(modelNameProblem)
|
||||
.find((p) => p !== null) ??
|
||||
(maxSubagentsValue(d.maxSubagents) === null
|
||||
? `max subagents is a whole number from 1 to ${MAX_SUBAGENTS}, or empty for no cap`
|
||||
: null)
|
||||
);
|
||||
}
|
||||
|
||||
export function draftIsEmpty(d: InferenceDraft): boolean {
|
||||
return (
|
||||
d.model.trim() === "" &&
|
||||
d.effort === "" &&
|
||||
parseModels(d.subagents).length === 0 &&
|
||||
d.maxSubagents.trim() === ""
|
||||
);
|
||||
}
|
||||
|
||||
// Only the filled fields: an empty one leaves the agent on its own value.
|
||||
export function draftBody(d: InferenceDraft): Settings {
|
||||
const body: Settings = {};
|
||||
if (d.model.trim() !== "") body.model = d.model.trim();
|
||||
if (d.effort !== "") body.effort = d.effort;
|
||||
const models = parseModels(d.subagents);
|
||||
if (models.length > 0) body.subagent_models = models;
|
||||
const max = maxSubagentsValue(d.maxSubagents);
|
||||
if (typeof max === "number") body.max_subagents = max;
|
||||
return body;
|
||||
}
|
||||
|
||||
export function InferenceFields({
|
||||
draft,
|
||||
onChange,
|
||||
}: {
|
||||
draft: InferenceDraft;
|
||||
onChange: (d: InferenceDraft) => void;
|
||||
}) {
|
||||
const problem = draftProblem(draft);
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
id="inference-model"
|
||||
label="model"
|
||||
value={draft.model}
|
||||
placeholder="e.g. opus"
|
||||
onInput={(model) => onChange({ ...draft, model })}
|
||||
/>
|
||||
<SelectField
|
||||
id="inference-effort"
|
||||
label="effort"
|
||||
value={draft.effort}
|
||||
options={EFFORT_OPTIONS}
|
||||
onChange={(effort) => onChange({ ...draft, effort })}
|
||||
/>
|
||||
<TextField
|
||||
id="inference-subagent-models"
|
||||
label="subagent models"
|
||||
value={draft.subagents}
|
||||
placeholder="e.g. haiku, sonnet"
|
||||
onInput={(subagents) => onChange({ ...draft, subagents })}
|
||||
/>
|
||||
<TextField
|
||||
id="inference-max-subagents"
|
||||
label="max subagents"
|
||||
value={draft.maxSubagents}
|
||||
placeholder="empty for no cap"
|
||||
pattern="[0-9]*"
|
||||
onInput={(maxSubagents) => onChange({ ...draft, maxSubagents })}
|
||||
/>
|
||||
{problem ? <p class="ui-agent-inference-problem">{problem}</p> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentInference({ agent }: { agent: string }) {
|
||||
const [view, setView] = useState<View | null>(null);
|
||||
const [error, setError] = useState<ProblemDetails | null>(null);
|
||||
|
|
@ -219,29 +313,17 @@ function InferenceForm({
|
|||
onClose: () => void;
|
||||
onSaved: () => void;
|
||||
}) {
|
||||
const [model, setModel] = useState(configured?.model ?? "");
|
||||
const [effort, setEffort] = useState(configured?.effort ?? "");
|
||||
const [subagents, setSubagents] = useState(
|
||||
(configured?.subagent_models ?? []).join(", "),
|
||||
);
|
||||
const [maxSubagents, setMaxSubagents] = useState(
|
||||
configured?.max_subagents?.toString() ?? "",
|
||||
);
|
||||
const [draft, setDraft] = useState<InferenceDraft>({
|
||||
model: configured?.model ?? "",
|
||||
effort: configured?.effort ?? "",
|
||||
subagents: (configured?.subagent_models ?? []).join(", "),
|
||||
maxSubagents: configured?.max_subagents?.toString() ?? "",
|
||||
});
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [problem, setProblem] = useState<ProblemDetails | null>(null);
|
||||
|
||||
const subagentList = parseModels(subagents);
|
||||
const formProblem =
|
||||
modelNameProblem(model.trim()) ??
|
||||
subagentList.map(modelNameProblem).find((p) => p !== null) ??
|
||||
(maxSubagentsValue(maxSubagents) === null
|
||||
? `max subagents is a whole number from 1 to ${MAX_SUBAGENTS}, or empty for no cap`
|
||||
: null);
|
||||
const empty =
|
||||
model.trim() === "" &&
|
||||
effort === "" &&
|
||||
subagentList.length === 0 &&
|
||||
maxSubagents.trim() === "";
|
||||
const formProblem = draftProblem(draft);
|
||||
const empty = draftIsEmpty(draft);
|
||||
|
||||
async function send(init: RequestInit) {
|
||||
setBusy(true);
|
||||
|
|
@ -261,16 +343,10 @@ function InferenceForm({
|
|||
|
||||
function save(e: Event) {
|
||||
e.preventDefault();
|
||||
const body: Settings = {};
|
||||
if (model.trim() !== "") body.model = model.trim();
|
||||
if (effort !== "") body.effort = effort;
|
||||
if (subagentList.length > 0) body.subagent_models = subagentList;
|
||||
const max = maxSubagentsValue(maxSubagents);
|
||||
if (typeof max === "number") body.max_subagents = max;
|
||||
void send({
|
||||
method: "PUT",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify(draftBody(draft)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -284,38 +360,7 @@ function InferenceForm({
|
|||
running.
|
||||
</p>
|
||||
<form class="ui-agent-inference-form" onSubmit={save}>
|
||||
<TextField
|
||||
id="inference-model"
|
||||
label="model"
|
||||
value={model}
|
||||
placeholder="e.g. opus"
|
||||
onInput={setModel}
|
||||
/>
|
||||
<SelectField
|
||||
id="inference-effort"
|
||||
label="effort"
|
||||
value={effort}
|
||||
options={EFFORT_OPTIONS}
|
||||
onChange={setEffort}
|
||||
/>
|
||||
<TextField
|
||||
id="inference-subagent-models"
|
||||
label="subagent models"
|
||||
value={subagents}
|
||||
placeholder="e.g. haiku, sonnet"
|
||||
onInput={setSubagents}
|
||||
/>
|
||||
<TextField
|
||||
id="inference-max-subagents"
|
||||
label="max subagents"
|
||||
value={maxSubagents}
|
||||
placeholder="empty for no cap"
|
||||
pattern="[0-9]*"
|
||||
onInput={setMaxSubagents}
|
||||
/>
|
||||
{formProblem ? (
|
||||
<p class="ui-agent-inference-problem">{formProblem}</p>
|
||||
) : null}
|
||||
<InferenceFields draft={draft} onChange={setDraft} />
|
||||
{problem ? (
|
||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||
) : null}
|
||||
|
|
|
|||
Loading…
Reference in a new issue