fix(agents): reject invalid goal token budgets (#104534)

* fix(agents): reject invalid goal token budgets

* test(agents): update goal assertion for session accessor

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
qingminlong
2026-07-11 17:05:04 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent d886059520
commit c59e24647c
2 changed files with 34 additions and 7 deletions
+28
View File
@@ -110,6 +110,34 @@ describe("goal tools", () => {
).toBeUndefined();
});
it.each(["42.9", "1abc", 0])(
"rejects invalid token budgets before creating a goal: %s",
async (tokenBudget) => {
const { config, template } = await createStoreConfig();
const tool = createCreateGoalTool({
agentSessionKey: "global",
runSessionKey: "global",
sessionAgentId: "research",
config,
});
const storePath = resolveStorePath(template, { agentId: "research" });
await upsertSessionEntry({
storePath,
sessionKey: "global",
entry: { sessionId: "sess-global", updatedAt: 1 },
});
await expect(
tool.execute("call-invalid-budget", {
objective: "ship global work",
token_budget: tokenBudget,
}),
).rejects.toThrow("token_budget must be a positive integer");
expect(getSessionEntry({ storePath, sessionKey: "global" })?.goal).toBeUndefined();
},
);
it("prefers scoped run session keys over the fallback session agent", async () => {
const { config, template } = await createStoreConfig();
const tool = createCreateGoalTool({
+6 -7
View File
@@ -18,7 +18,7 @@ import {
type AnyAgentTool,
ToolInputError,
jsonResult,
readNumberParam,
readPositiveIntegerParam,
readStringParam,
} from "./common.js";
@@ -40,7 +40,8 @@ const CreateGoalToolSchema = Type.Object({
description: "Concrete objective to pursue. Create only when explicitly requested.",
}),
token_budget: Type.Optional(
Type.Number({
Type.Integer({
minimum: 1,
description: "Optional positive token budget for this goal.",
}),
),
@@ -103,11 +104,9 @@ export function createCreateGoalTool(options: GoalToolOptions): AnyAgentTool {
execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>;
const objective = readStringParam(params, "objective", { required: true });
const tokenBudget = readNumberParam(params, "token_budget", { integer: true });
if (tokenBudget !== undefined && tokenBudget <= 0) {
// Budgets are positive limits; zero would immediately make accounting ambiguous.
throw new ToolInputError("token_budget must be positive");
}
const tokenBudget = readPositiveIntegerParam(params, "token_budget", {
message: "token_budget must be a positive integer",
});
const scope = resolveGoalSessionScope(options);
const goal = await createSessionGoal({
...scope,