feat(coding-agent): added eager todo configuration and per-turn tool choice overrides

- Added 'todo.eager' configuration setting to automatically create a comprehensive todo list after the first user message.
- Added 'buildNamedToolChoice' utility function to build provider-aware tool choice constraints for named tools.
- Modified tool choice resolution to support per-turn tool choice overrides via consumeNextToolChoiceOverride() method.
- Implemented eager todo enforcement mechanism that injects a synthetic prompt to encourage todo creation when conditions are met.
- Extracted tool choice building logic into reusable utility module for better code organization.
- Added comprehensive test coverage for eager todo enforcement functionality in AgentSession.
This commit is contained in:
can1357
2026-03-11 00:54:55 +01:00
parent 08a4fe78a2
commit bb0026cb32
8 changed files with 404 additions and 24 deletions
+2 -18
View File
@@ -5,7 +5,6 @@
*/
import path from "node:path";
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
import type { TSchema } from "@sinclair/typebox";
import Ajv, { type ValidateFunction } from "ajv";
@@ -28,6 +27,7 @@ import { type ContextFileEntry, truncateTail } from "../tools";
import { jtdToJsonSchema } from "../tools/jtd-to-json-schema";
import { ToolAbortError } from "../tools/tool-errors";
import type { EventBus } from "../utils/event-bus";
import { buildNamedToolChoice } from "../utils/tool-choice";
import { subprocessToolRegistry } from "./subprocess-tool-registry";
import {
type AgentDefinition,
@@ -117,22 +117,6 @@ function getReportFindingKey(value: unknown): string | null {
return `${filePath}:${lineStart}:${lineEnd}:${priority ?? ""}:${title}`;
}
function buildSubmitResultToolChoice(model?: Model<Api>): ToolChoice | undefined {
if (!model) return undefined;
if (
model.api === "openai-codex-responses" ||
model.api === "openai-responses" ||
model.api === "openai-completions" ||
model.api === "azure-openai-responses"
) {
return { type: "function", name: "submit_result" };
}
if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") {
return { type: "tool", name: "submit_result" };
}
return undefined;
}
/** Options for subagent execution */
export interface ExecutorOptions {
cwd: string;
@@ -1091,7 +1075,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
await session.prompt(task);
await session.waitForIdle();
const reminderToolChoice = buildSubmitResultToolChoice(session.model);
const reminderToolChoice = buildNamedToolChoice("submit_result", session.model);
let retryCount = 0;
while (!submitResultCalled && retryCount < MAX_SUBMIT_RESULT_RETRIES && !abortSignal.aborted) {