refactor: split inbound and reload pipelines into staged modules

This commit is contained in:
Peter Steinberger
2026-03-02 21:54:53 +00:00
parent 99a3db6ba9
commit 55a2d12f40
9 changed files with 791 additions and 472 deletions

View File

@@ -9,6 +9,7 @@ import {
HARD_MAX_TOOL_RESULT_CHARS,
truncateToolResultMessage,
} from "./pi-embedded-runner/tool-result-truncation.js";
import { createPendingToolCallState } from "./session-tool-result-state.js";
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
@@ -106,7 +107,7 @@ export function installSessionToolResultGuard(
getPendingIds: () => string[];
} {
const originalAppend = sessionManager.appendMessage.bind(sessionManager);
const pending = new Map<string, string | undefined>();
const pendingState = createPendingToolCallState();
const persistMessage = (message: AgentMessage) => {
const transformer = opts?.transformMessageForPersistence;
return transformer ? transformer(message) : message;
@@ -142,11 +143,11 @@ export function installSessionToolResultGuard(
};
const flushPendingToolResults = () => {
if (pending.size === 0) {
if (pendingState.size() === 0) {
return;
}
if (allowSyntheticToolResults) {
for (const [id, name] of pending.entries()) {
for (const [id, name] of pendingState.entries()) {
const synthetic = makeMissingToolResult({ toolCallId: id, toolName: name });
const flushed = applyBeforeWriteHook(
persistToolResult(persistMessage(synthetic), {
@@ -160,7 +161,7 @@ export function installSessionToolResultGuard(
}
}
}
pending.clear();
pendingState.clear();
};
const guardedAppend = (message: AgentMessage) => {
@@ -171,7 +172,7 @@ export function installSessionToolResultGuard(
allowedToolNames: opts?.allowedToolNames,
});
if (sanitized.length === 0) {
if (pending.size > 0) {
if (pendingState.shouldFlushForSanitizedDrop()) {
flushPendingToolResults();
}
return undefined;
@@ -182,9 +183,9 @@ export function installSessionToolResultGuard(
if (nextRole === "toolResult") {
const id = extractToolResultId(nextMessage as Extract<AgentMessage, { role: "toolResult" }>);
const toolName = id ? pending.get(id) : undefined;
const toolName = id ? pendingState.getToolName(id) : undefined;
if (id) {
pending.delete(id);
pendingState.delete(id);
}
const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName);
// Apply hard size cap before persistence to prevent oversized tool results
@@ -221,11 +222,11 @@ export function installSessionToolResultGuard(
// synthetic results (e.g. OpenAI) accumulate stale pending state when a user message
// interrupts in-flight tool calls, leaving orphaned tool_use blocks in the transcript
// that cause API 400 errors on subsequent requests.
if (pending.size > 0 && (toolCalls.length === 0 || nextRole !== "assistant")) {
if (pendingState.shouldFlushBeforeNonToolResult(nextRole, toolCalls.length)) {
flushPendingToolResults();
}
// If new tool calls arrive while older ones are pending, flush the old ones first.
if (pending.size > 0 && toolCalls.length > 0) {
if (pendingState.shouldFlushBeforeNewToolCalls(toolCalls.length)) {
flushPendingToolResults();
}
@@ -243,9 +244,7 @@ export function installSessionToolResultGuard(
}
if (toolCalls.length > 0) {
for (const call of toolCalls) {
pending.set(call.id, call.name);
}
pendingState.trackToolCalls(toolCalls);
}
return result;
@@ -256,6 +255,6 @@ export function installSessionToolResultGuard(
return {
flushPendingToolResults,
getPendingIds: () => Array.from(pending.keys()),
getPendingIds: pendingState.getPendingIds,
};
}

View File

@@ -0,0 +1,40 @@
export type PendingToolCall = { id: string; name?: string };
export type PendingToolCallState = {
size: () => number;
entries: () => IterableIterator<[string, string | undefined]>;
getToolName: (id: string) => string | undefined;
delete: (id: string) => void;
clear: () => void;
trackToolCalls: (calls: PendingToolCall[]) => void;
getPendingIds: () => string[];
shouldFlushForSanitizedDrop: () => boolean;
shouldFlushBeforeNonToolResult: (nextRole: unknown, toolCallCount: number) => boolean;
shouldFlushBeforeNewToolCalls: (toolCallCount: number) => boolean;
};
export function createPendingToolCallState(): PendingToolCallState {
const pending = new Map<string, string | undefined>();
return {
size: () => pending.size,
entries: () => pending.entries(),
getToolName: (id: string) => pending.get(id),
delete: (id: string) => {
pending.delete(id);
},
clear: () => {
pending.clear();
},
trackToolCalls: (calls: PendingToolCall[]) => {
for (const call of calls) {
pending.set(call.id, call.name);
}
},
getPendingIds: () => Array.from(pending.keys()),
shouldFlushForSanitizedDrop: () => pending.size > 0,
shouldFlushBeforeNonToolResult: (nextRole: unknown, toolCallCount: number) =>
pending.size > 0 && (toolCallCount === 0 || nextRole !== "assistant"),
shouldFlushBeforeNewToolCalls: (toolCallCount: number) => pending.size > 0 && toolCallCount > 0,
};
}