@caupulican/pi-agent-core 0.91.4 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loop.d.ts +2 -1
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +52 -96
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +4 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -0
- package/dist/agent.js.map +1 -1
- package/dist/provider-request-planner.d.ts +2 -0
- package/dist/provider-request-planner.d.ts.map +1 -1
- package/dist/provider-request-planner.js +7 -1
- package/dist/provider-request-planner.js.map +1 -1
- package/dist/tool-failure-recovery-protocol.d.ts +0 -7
- package/dist/tool-failure-recovery-protocol.d.ts.map +1 -1
- package/dist/tool-failure-recovery-protocol.js +1 -25
- package/dist/tool-failure-recovery-protocol.js.map +1 -1
- package/dist/types.d.ts +26 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/package.json +22 -2
package/dist/agent-loop.d.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import { EventStream } from "@caupulican/pi-ai/event-stream";
|
|
6
6
|
import { ToolFailureRecoveryGate } from "./tool-failure-recovery-gate.ts";
|
|
7
7
|
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentToolCall, StreamFn, ToolCallRepairInfo } from "./types.ts";
|
|
8
|
-
export { narrowRequestMaxTokens, resolveRequestPreflightMaxTokens } from "./provider-request-planner.ts";
|
|
8
|
+
export { composeRequestSystemPrompt, narrowRequestMaxTokens, resolveRequestPreflightMaxTokens, } from "./provider-request-planner.ts";
|
|
9
9
|
export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
|
|
10
10
|
/** Bounded no-progress state retained across host-owned continuations of one logical prompt. */
|
|
11
11
|
export interface AgentLoopContinuationState {
|
|
12
|
+
providerTurns: number;
|
|
12
13
|
stallWindow: string[];
|
|
13
14
|
toolFailureRecoveryGate: ToolFailureRecoveryGate;
|
|
14
15
|
}
|
package/dist/agent-loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAiC7D,OAAO,EAGN,uBAAuB,EAGvB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAEb,QAAQ,EACR,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAIpB,OAAO,EACN,0BAA0B,EAC1B,sBAAsB,EACtB,gCAAgC,GAChC,MAAM,+BAA+B,CAAC;AAEvC,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE,gGAAgG;AAChG,MAAM,WAAW,0BAA0B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,EAAE,uBAAuB,CAAC;CACjD;AAED,wBAAgB,gCAAgC,IAAI,0BAA0B,CAE7E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAIzC;AAuBD,wBAAsB,YAAY,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,EACnB,iBAAiB,GAAE,0BAA+D,GAChF,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBzB;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,EACnB,iBAAiB,GAAE,0BAA+D,GAChF,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAoVD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAExC;AAqjCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
|
package/dist/agent-loop.js
CHANGED
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
import { EventStream } from "@caupulican/pi-ai/event-stream";
|
|
6
6
|
import { formatToolRepairStandingRule, REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE, } from "@caupulican/pi-ai/tool-repair-registry";
|
|
7
7
|
import { ToolArgumentValidationError, validateToolArguments, } from "@caupulican/pi-ai/validation";
|
|
8
|
-
import { assistantMessageText, collapseDegenerateAssistantMessage,
|
|
8
|
+
import { assistantMessageText, collapseDegenerateAssistantMessage, shouldAbortDegenerateStream, } from "./degenerate-assistant-text.js";
|
|
9
9
|
import { startPlannedAgentProviderRequest } from "./provider-request-planner.js";
|
|
10
10
|
import { assessToolFailure, clearToolFailure, createRepeatedToolFailureResult, createToolFailureMemoryTracker, createToolFailureOperationExhaustedResult, createToolFailureRecoveryExhaustedResult, createToolFailureResult, getUnresolvedToolFailure, normalizeToolSignature, rememberToolFailure, toolFailureCorrection, } from "./tool-failure-memory.js";
|
|
11
11
|
import { TOOL_FAILURE_RECOVERY_ACCOUNTING_WAVE_SIZE, ToolFailureRecoveryGate, } from "./tool-failure-recovery-gate.js";
|
|
12
|
-
import { appendMandatoryToolFailureDeliveryPrompt } from "./tool-failure-recovery-protocol.js";
|
|
13
12
|
import { rejectNativeToolProtocolResidue } from "./tool-protocol-residue.js";
|
|
14
|
-
import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
|
|
13
|
+
import { DEFAULT_MAX_PROVIDER_TURNS, DEFAULT_MAX_STALL_TURNS } from "./types.js";
|
|
15
14
|
import { createEmptyUsage } from "./usage.js";
|
|
16
|
-
export { narrowRequestMaxTokens, resolveRequestPreflightMaxTokens } from "./provider-request-planner.js";
|
|
15
|
+
export { composeRequestSystemPrompt, narrowRequestMaxTokens, resolveRequestPreflightMaxTokens, } from "./provider-request-planner.js";
|
|
17
16
|
export function createAgentLoopContinuationState() {
|
|
18
|
-
return { stallWindow: [], toolFailureRecoveryGate: new ToolFailureRecoveryGate() };
|
|
17
|
+
return { providerTurns: 0, stallWindow: [], toolFailureRecoveryGate: new ToolFailureRecoveryGate() };
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
20
|
* Start an agent loop with a new prompt message.
|
|
@@ -96,30 +95,17 @@ function createLoopFailureMessage(error, config, aborted) {
|
|
|
96
95
|
timestamp: Date.now(),
|
|
97
96
|
};
|
|
98
97
|
}
|
|
99
|
-
function mandatoryDeliveryReportsHalt(message, halt) {
|
|
100
|
-
const raw = assistantMessageText(message);
|
|
101
|
-
if (!raw.trim())
|
|
102
|
-
return false;
|
|
103
|
-
if (isCollapsedDegenerateAssistantMessage(message) || isDegenerateRepeatedText(raw))
|
|
104
|
-
return false;
|
|
105
|
-
const text = raw.toLowerCase();
|
|
106
|
-
if (text.includes("recovery"))
|
|
107
|
-
return true;
|
|
108
|
-
if (text.includes(halt.record.failureCode.toLowerCase()))
|
|
109
|
-
return true;
|
|
110
|
-
const diagnostic = halt.record.diagnostic ?? halt.diagnostic;
|
|
111
|
-
return diagnostic !== undefined && text.includes(diagnostic.toLowerCase().slice(0, 40));
|
|
112
|
-
}
|
|
113
98
|
function createMandatoryRecoveryDeliveryFallback(halt, config) {
|
|
114
99
|
const diagnostic = halt.record.diagnostic ?? halt.diagnostic;
|
|
100
|
+
return createLocalDiagnosticMessage(config, `Tool recovery stopped for ${halt.record.tool}: ${diagnostic} Required recovery: ${halt.record.correction}`);
|
|
101
|
+
}
|
|
102
|
+
function createProviderTurnLimitMessage(config, providerTurns) {
|
|
103
|
+
return createLocalDiagnosticMessage(config, `Provider turn limit (${providerTurns}) reached. The harness stopped before another paid model request; continue explicitly if more work is needed.`);
|
|
104
|
+
}
|
|
105
|
+
function createLocalDiagnosticMessage(config, text) {
|
|
115
106
|
return {
|
|
116
107
|
role: "assistant",
|
|
117
|
-
content: [
|
|
118
|
-
{
|
|
119
|
-
type: "text",
|
|
120
|
-
text: `Tool recovery stopped for ${halt.record.tool}: ${diagnostic} Required recovery: ${halt.record.correction}`,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
108
|
+
content: [{ type: "text", text }],
|
|
123
109
|
api: config.model.api,
|
|
124
110
|
provider: config.model.provider,
|
|
125
111
|
model: config.model.id,
|
|
@@ -128,6 +114,14 @@ function createMandatoryRecoveryDeliveryFallback(halt, config) {
|
|
|
128
114
|
timestamp: Date.now(),
|
|
129
115
|
};
|
|
130
116
|
}
|
|
117
|
+
async function emitTerminalLocalMessage(message, newMessages, emit, startTurn) {
|
|
118
|
+
if (startTurn)
|
|
119
|
+
await emit({ type: "turn_start" });
|
|
120
|
+
await emit({ type: "message_start", message });
|
|
121
|
+
await emit({ type: "message_end", message });
|
|
122
|
+
await emit({ type: "turn_end", message, toolResults: [] });
|
|
123
|
+
await emit({ type: "agent_end", messages: newMessages });
|
|
124
|
+
}
|
|
131
125
|
function createAgentStream() {
|
|
132
126
|
return new EventStream((event) => event.type === "agent_end", (event) => (event.type === "agent_end" ? event.messages : []));
|
|
133
127
|
}
|
|
@@ -200,13 +194,13 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
200
194
|
// window slides past it. Counts only turns that issued tool calls, so varied/long work never trips
|
|
201
195
|
// it. `0` disables.
|
|
202
196
|
const stallLimit = config.maxStallTurns ?? DEFAULT_MAX_STALL_TURNS;
|
|
197
|
+
const providerTurnLimit = config.maxProviderTurns ?? DEFAULT_MAX_PROVIDER_TURNS;
|
|
203
198
|
const stallWindow = continuationState.stallWindow;
|
|
204
199
|
const validationFailureTracker = { repeats: 0 };
|
|
205
200
|
const repairTeachTracker = new Map();
|
|
206
201
|
let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
|
|
207
202
|
const toolFailureRecoveryGate = continuationState.toolFailureRecoveryGate;
|
|
208
203
|
toolFailureRecoveryGate.restoreFromMessages(currentContext.messages);
|
|
209
|
-
let mandatoryRecoveryDeliveryPending = false;
|
|
210
204
|
let lastSuccessfulTextProtocolBatch;
|
|
211
205
|
// Check for steering messages at start (user may have typed while waiting)
|
|
212
206
|
let pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
@@ -232,26 +226,20 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
232
226
|
}
|
|
233
227
|
pendingMessages = [];
|
|
234
228
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
229
|
+
if (providerTurnLimit > 0 && continuationState.providerTurns >= providerTurnLimit) {
|
|
230
|
+
const fallback = createProviderTurnLimitMessage(config, continuationState.providerTurns);
|
|
231
|
+
currentContext.messages.push(fallback);
|
|
232
|
+
newMessages.push(fallback);
|
|
233
|
+
config.onRunawayStop?.({
|
|
234
|
+
reason: "provider_turn_limit",
|
|
235
|
+
signature: "provider_turn_limit",
|
|
236
|
+
repeats: continuationState.providerTurns,
|
|
237
|
+
});
|
|
238
|
+
await emitTerminalLocalMessage(fallback, newMessages, emit, false);
|
|
239
|
+
return;
|
|
241
240
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
...currentContext,
|
|
245
|
-
systemPrompt: appendMandatoryToolFailureDeliveryPrompt(currentContext.systemPrompt, {
|
|
246
|
-
tool: recoveryDeliveryHalt.record.tool,
|
|
247
|
-
failureCode: recoveryDeliveryHalt.record.failureCode,
|
|
248
|
-
diagnostic: recoveryDeliveryHalt.record.diagnostic ?? recoveryDeliveryHalt.diagnostic,
|
|
249
|
-
requiredAction: recoveryDeliveryHalt.record.correction,
|
|
250
|
-
}),
|
|
251
|
-
tools: [],
|
|
252
|
-
}
|
|
253
|
-
: currentContext;
|
|
254
|
-
const message = await streamAssistantResponse(responseContext, config, signal, emit, streamFn);
|
|
241
|
+
continuationState.providerTurns++;
|
|
242
|
+
const message = await streamAssistantResponse(currentContext, config, signal, emit, streamFn);
|
|
255
243
|
newMessages.push(message);
|
|
256
244
|
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
257
245
|
await emit({ type: "turn_end", message, toolResults: [] });
|
|
@@ -261,30 +249,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
261
249
|
// Check for tool calls
|
|
262
250
|
const toolCalls = message.content.filter((c) => c.type === "toolCall");
|
|
263
251
|
const toolResults = [];
|
|
264
|
-
|
|
265
|
-
if (!recoveryDeliveryHalt) {
|
|
266
|
-
throw new Error("Mandatory recovery delivery continued without a recovery halt");
|
|
267
|
-
}
|
|
268
|
-
if (toolCalls.length > 0) {
|
|
269
|
-
toolResults.push(...(await rejectRecoveryDeliveryToolCalls(toolCalls, recoveryDeliveryHalt, emit)));
|
|
270
|
-
for (const result of toolResults) {
|
|
271
|
-
currentContext.messages.push(result);
|
|
272
|
-
newMessages.push(result);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
await emit({ type: "turn_end", message, toolResults });
|
|
276
|
-
if (toolCalls.length > 0 || !mandatoryDeliveryReportsHalt(message, recoveryDeliveryHalt)) {
|
|
277
|
-
const fallback = createMandatoryRecoveryDeliveryFallback(recoveryDeliveryHalt, config);
|
|
278
|
-
currentContext.messages.push(fallback);
|
|
279
|
-
newMessages.push(fallback);
|
|
280
|
-
await emit({ type: "turn_start" });
|
|
281
|
-
await emit({ type: "message_start", message: fallback });
|
|
282
|
-
await emit({ type: "message_end", message: fallback });
|
|
283
|
-
await emit({ type: "turn_end", message: fallback, toolResults: [] });
|
|
284
|
-
}
|
|
285
|
-
await emit({ type: "agent_end", messages: newMessages });
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
252
|
+
let recoveryHalt;
|
|
288
253
|
hasMoreToolCalls = false;
|
|
289
254
|
if (toolCalls.length > 0) {
|
|
290
255
|
const textProtocolBatch = toolCalls.every((toolCall) => toolCall.source === "text-protocol");
|
|
@@ -296,8 +261,9 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
296
261
|
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, previousSuccessfulTextProtocolResults, signal, emit);
|
|
297
262
|
toolResults.push(...executedToolBatch.messages);
|
|
298
263
|
if (toolFailureRecoveryGate.isHalted()) {
|
|
299
|
-
|
|
300
|
-
|
|
264
|
+
recoveryHalt = toolFailureRecoveryGate.getHalt();
|
|
265
|
+
if (!recoveryHalt)
|
|
266
|
+
throw new Error("Tool recovery halted without a failure record");
|
|
301
267
|
}
|
|
302
268
|
else {
|
|
303
269
|
hasMoreToolCalls = !executedToolBatch.terminate;
|
|
@@ -323,15 +289,22 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
323
289
|
lastSuccessfulTextProtocolBatch = undefined;
|
|
324
290
|
}
|
|
325
291
|
await emit({ type: "turn_end", message, toolResults });
|
|
292
|
+
if (recoveryHalt) {
|
|
293
|
+
const fallback = createMandatoryRecoveryDeliveryFallback(recoveryHalt, config);
|
|
294
|
+
currentContext.messages.push(fallback);
|
|
295
|
+
newMessages.push(fallback);
|
|
296
|
+
await emitTerminalLocalMessage(fallback, newMessages, emit, true);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
326
299
|
// Runaway-loop backstop (cost guard): detect a model stuck repeating one action.
|
|
327
|
-
if (
|
|
300
|
+
if (stallLimit > 0 && toolCalls.length > 0) {
|
|
328
301
|
const signature = normalizeToolSignature(toolCalls.map((c) => [c.name, c.arguments ?? null]));
|
|
329
302
|
stallWindow.push(signature);
|
|
330
303
|
if (stallWindow.length > stallLimit * STALL_WINDOW_PERIODS)
|
|
331
304
|
stallWindow.shift();
|
|
332
305
|
const repeats = stallWindow.reduce((n, s) => (s === signature ? n + 1 : n), 0);
|
|
333
306
|
if (repeats >= stallLimit) {
|
|
334
|
-
config.onRunawayStop?.({ signature, repeats });
|
|
307
|
+
config.onRunawayStop?.({ reason: "repeated_tool_call", signature, repeats });
|
|
335
308
|
await emit({ type: "agent_end", messages: newMessages });
|
|
336
309
|
return;
|
|
337
310
|
}
|
|
@@ -354,13 +327,12 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
354
327
|
reasoning: nextTurnSnapshot.thinkingLevel ?? config.reasoning,
|
|
355
328
|
};
|
|
356
329
|
}
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}))) {
|
|
330
|
+
if (await config.shouldStopAfterTurn?.({
|
|
331
|
+
message,
|
|
332
|
+
toolResults,
|
|
333
|
+
context: currentContext,
|
|
334
|
+
newMessages,
|
|
335
|
+
})) {
|
|
364
336
|
await emit({ type: "agent_end", messages: newMessages });
|
|
365
337
|
return;
|
|
366
338
|
}
|
|
@@ -1166,20 +1138,4 @@ async function emitToolResultMessage(toolResultMessage, emit) {
|
|
|
1166
1138
|
await emit({ type: "message_start", message: toolResultMessage });
|
|
1167
1139
|
await emit({ type: "message_end", message: toolResultMessage });
|
|
1168
1140
|
}
|
|
1169
|
-
async function rejectRecoveryDeliveryToolCalls(toolCalls, halt, emit) {
|
|
1170
|
-
const messages = [];
|
|
1171
|
-
for (const toolCall of toolCalls) {
|
|
1172
|
-
await emitToolExecutionStart(toolCall, emit);
|
|
1173
|
-
const finalized = {
|
|
1174
|
-
toolCall,
|
|
1175
|
-
result: createToolFailureRecoveryExhaustedResult(halt.record, "Tool execution is disabled during the mandatory recovery delivery turn."),
|
|
1176
|
-
isError: true,
|
|
1177
|
-
};
|
|
1178
|
-
await emitToolExecutionEnd(finalized, emit);
|
|
1179
|
-
const message = createToolResultMessage(finalized);
|
|
1180
|
-
await emitToolResultMessage(message, emit);
|
|
1181
|
-
messages.push(message);
|
|
1182
|
-
}
|
|
1183
|
-
return messages;
|
|
1184
|
-
}
|
|
1185
1141
|
//# sourceMappingURL=agent-loop.js.map
|