add back ask-human support

This commit is contained in:
Ramnique Singh 2025-11-18 02:28:49 +05:30
parent 39f0f5af79
commit 36530c2ccd
5 changed files with 131 additions and 24 deletions

View file

@ -12,7 +12,6 @@ import { getProvider } from "./models.js";
import { LlmStepStreamEvent } from "../entities/llm-step-events.js";
import { execTool } from "./exec-tool.js";
import { RunEvent } from "../entities/run-events.js";
import { CopilotAgent } from "../assistant/agent.js";
import { BuiltinTools } from "./builtin-tools.js";
export async function mapAgentTool(t: z.infer<typeof ToolAttachment>): Promise<Tool> {
@ -36,6 +35,14 @@ export async function mapAgentTool(t: z.infer<typeof ToolAttachment>): Promise<T
}),
});
case "builtin":
if (t.name === "ask-human") {
return tool({
description: "Ask a human before proceeding",
inputSchema: z.object({
question: z.string().describe("The question to ask the human"),
}),
});
}
const match = BuiltinTools[t.name];
if (!match) {
throw new Error(`Unknown builtin tool: ${t.name}`);
@ -129,6 +136,30 @@ export class StreamStepMessageBuilder {
}
}
function normaliseAskHumanToolCall(message: z.infer<typeof AssistantMessage>) {
if (typeof message.content === "string") {
return;
}
let askHumanToolCall: z.infer<typeof ToolCallPart> | null = null;
const newParts = [];
for (const part of message.content as z.infer<typeof AssistantContentPart>[]) {
if (part.type === "tool-call" && part.toolName === "ask-human") {
if (!askHumanToolCall) {
askHumanToolCall = part;
} else {
(askHumanToolCall as z.infer<typeof ToolCallPart>).arguments += "\n" + part.arguments;
}
break;
} else {
newParts.push(part);
}
}
if (askHumanToolCall) {
newParts.push(askHumanToolCall);
}
message.content = newParts;
}
export async function loadAgent(id: string): Promise<z.infer<typeof Agent>> {
const agentPath = path.join(WorkDir, "agents", `${id}.json`);
const agent = fs.readFileSync(agentPath, "utf8");
@ -240,6 +271,7 @@ export async function* streamAgentTurn(opts: {
// build and emit final message from agent response
const msg = messageBuilder.get();
normaliseAskHumanToolCall(msg);
messages.push(msg);
yield {
type: "message",
@ -266,7 +298,11 @@ export async function* streamAgentTurn(opts: {
});
}
// first, handle tool calls other than ask-human
for (const call of mappedToolCalls) {
if (call.toolCall.toolName === "ask-human") {
continue;
}
const { agentTool, toolCall } = call;
yield {
type: "tool-invocation",
@ -292,13 +328,24 @@ export async function* streamAgentTurn(opts: {
};
}
// then, handle ask-human (only first one)
const askHumanCall = mappedToolCalls.filter(call => call.toolCall.toolName === "ask-human")[0];
if (askHumanCall) {
yield {
type: "pause-for-human-input",
toolCallId: askHumanCall.toolCall.toolCallId,
question: askHumanCall.toolCall.arguments.question as string,
};
return;
}
// if the agent response had tool calls, replay this agent
if (hasToolCalls) {
continue;
}
// otherwise, break
break;
return;
}
}
@ -314,12 +361,12 @@ async function* streamLlm(
system: instructions,
tools,
stopWhen: stepCountIs(1),
providerOptions: {
openai: {
reasoningEffort: "low",
reasoningSummary: "auto",
},
}
// providerOptions: {
// openai: {
// reasoningEffort: "low",
// reasoningSummary: "auto",
// },
// }
});
for await (const event of fullStream) {
// console.log("\n\n\t>>>>\t\tstream event", JSON.stringify(event));