my harness for niri
1import { emit } from "../stream.js"
2import { buildToolHandlers } from "./loop-tool-registry.js"
3import { executeToolCall, pushToolMessage } from "./loop-tool-runtime.js"
4import type { FunctionToolCall } from "./loop-shared.js"
5import type { LoopHooks, LoopState } from "./types.js"
6
7function skipRemainingToolCalls(
8 convId: number,
9 state: LoopState,
10 calls: readonly FunctionToolCall[],
11 startIndex: number,
12 source: string,
13): void {
14 calls.slice(startIndex).forEach((pendingCall) => {
15 const skippedContent = `skipped: interrupted by incoming ${source} event.`
16 const result = pushToolMessage(convId, state, pendingCall, skippedContent)
17 emit({ type: "tool", name: pendingCall.function.name, args: { _skipped: true }, result })
18 })
19}
20
21function maybeInterruptAfterTool(
22 convId: number,
23 state: LoopState,
24 hooks: LoopHooks,
25 calls: readonly FunctionToolCall[],
26 nextIndex: number,
27): boolean {
28 if (state.pendingInputs.length === 0) return false
29
30 const incoming = state.pendingInputs.shift()!
31 hooks.injectIncomingEvent(convId, incoming)
32 skipRemainingToolCalls(convId, state, calls, nextIndex, incoming.source)
33 return true
34}
35
36/**
37 * Processes all function tool calls requested in one assistant turn.
38 *
39 * @param convId - Active conversation id.
40 * @param state - Mutable loop state.
41 * @param hooks - Loop hooks for side effects and event flow.
42 * @param calls - Function tool calls to execute in order.
43 * @returns `true` when a `rest` tool call ended the loop, otherwise `false`.
44 */
45export async function processToolCalls(
46 convId: number,
47 state: LoopState,
48 hooks: LoopHooks,
49 calls: readonly FunctionToolCall[],
50): Promise<boolean> {
51 const handlers = buildToolHandlers()
52
53 for (let i = 0; i < calls.length; i++) {
54 const call = calls[i]!
55 const outcome = await executeToolCall(convId, state, hooks, handlers, call)
56
57 if (outcome.shouldRest) return true
58 if (outcome.isWait) continue
59
60 const interrupted = maybeInterruptAfterTool(convId, state, hooks, calls, i + 1)
61 if (interrupted) return false
62 }
63
64 return false
65}