@caupulican/pi-agent-core 0.86.17 → 0.90.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.
Files changed (56) hide show
  1. package/dist/agent-loop.d.ts +2 -13
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +99 -77
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +5 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +4 -0
  8. package/dist/agent.js.map +1 -1
  9. package/dist/compaction/branch-summarization.d.ts.map +1 -1
  10. package/dist/compaction/branch-summarization.js +11 -15
  11. package/dist/compaction/branch-summarization.js.map +1 -1
  12. package/dist/compaction/compaction.d.ts +3 -0
  13. package/dist/compaction/compaction.d.ts.map +1 -1
  14. package/dist/compaction/compaction.js +37 -46
  15. package/dist/compaction/compaction.js.map +1 -1
  16. package/dist/compaction/loop.d.ts +4 -0
  17. package/dist/compaction/loop.d.ts.map +1 -1
  18. package/dist/compaction/loop.js +7 -2
  19. package/dist/compaction/loop.js.map +1 -1
  20. package/dist/compaction/utils.d.ts +1 -1
  21. package/dist/compaction/utils.d.ts.map +1 -1
  22. package/dist/compaction/utils.js +13 -41
  23. package/dist/compaction/utils.js.map +1 -1
  24. package/dist/compaction/verification.js +2 -2
  25. package/dist/compaction/verification.js.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/provider-request-estimator.d.ts +6 -0
  31. package/dist/provider-request-estimator.d.ts.map +1 -0
  32. package/dist/provider-request-estimator.js +105 -0
  33. package/dist/provider-request-estimator.js.map +1 -0
  34. package/dist/provider-request-planner.d.ts +15 -0
  35. package/dist/provider-request-planner.d.ts.map +1 -0
  36. package/dist/provider-request-planner.js +168 -0
  37. package/dist/provider-request-planner.js.map +1 -0
  38. package/dist/provider-tool-projection.d.ts +7 -0
  39. package/dist/provider-tool-projection.d.ts.map +1 -0
  40. package/dist/provider-tool-projection.js +140 -0
  41. package/dist/provider-tool-projection.js.map +1 -0
  42. package/dist/tool-failure-memory.d.ts.map +1 -1
  43. package/dist/tool-failure-memory.js +73 -61
  44. package/dist/tool-failure-memory.js.map +1 -1
  45. package/dist/tool-failure-recovery-gate.d.ts +1 -1
  46. package/dist/tool-failure-recovery-gate.d.ts.map +1 -1
  47. package/dist/tool-failure-recovery-gate.js +8 -8
  48. package/dist/tool-failure-recovery-gate.js.map +1 -1
  49. package/dist/tool-failure-recovery-protocol.d.ts +15 -0
  50. package/dist/tool-failure-recovery-protocol.d.ts.map +1 -0
  51. package/dist/tool-failure-recovery-protocol.js +43 -0
  52. package/dist/tool-failure-recovery-protocol.js.map +1 -0
  53. package/dist/types.d.ts +59 -2
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/package.json +2 -2
@@ -3,7 +3,8 @@
3
3
  * Transforms to Message[] only at the LLM call boundary.
4
4
  */
5
5
  import { EventStream } from "@caupulican/pi-ai/event-stream";
6
- import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentToolCall, RequestPreflightContext, RequestPreflightResult, StreamFn, ToolCallRepairInfo } from "./types.ts";
6
+ import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentToolCall, StreamFn, ToolCallRepairInfo } from "./types.ts";
7
+ export { resolveRequestPreflightMaxTokens } from "./provider-request-planner.ts";
7
8
  export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
8
9
  /**
9
10
  * Start an agent loop with a new prompt message.
@@ -21,18 +22,6 @@ export declare function agentLoop(prompts: AgentMessage[], context: AgentContext
21
22
  export declare function agentLoopContinue(context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn): EventStream<AgentEvent, AgentMessage[]>;
22
23
  export declare function runAgentLoop(prompts: AgentMessage[], context: AgentContext, config: AgentLoopConfig, emit: AgentEventSink, signal?: AbortSignal, streamFn?: StreamFn): Promise<AgentMessage[]>;
23
24
  export declare function runAgentLoopContinue(context: AgentContext, config: AgentLoopConfig, emit: AgentEventSink, signal?: AbortSignal, streamFn?: StreamFn): Promise<AgentMessage[]>;
24
- /**
25
- * Apply one request-local preflight without mutating persistent loop configuration.
26
- * Shared with isolated tool-free provider calls so every transport boundary has identical
27
- * validation and non-widening semantics.
28
- */
29
- export declare function resolveRequestPreflightMaxTokens(options: {
30
- requestPreflight?: (context: RequestPreflightContext, signal?: AbortSignal) => RequestPreflightResult | undefined | Promise<RequestPreflightResult | undefined>;
31
- model: RequestPreflightContext["model"];
32
- context: RequestPreflightContext["context"];
33
- maxTokens?: number;
34
- signal?: AbortSignal;
35
- }): Promise<number | undefined>;
36
25
  /**
37
26
  * Start one provider request through the canonical agent-loop boundary.
38
27
  *
@@ -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;AAmC7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAEb,uBAAuB,EACvB,sBAAsB,EACtB,QAAQ,EACR,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE;;;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,GACjB,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,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAyFD;;;;GAIG;AACH,wBAAsB,gCAAgC,CAAC,OAAO,EAAE;IAC/D,gBAAgB,CAAC,EAAE,CAClB,OAAO,EAAE,uBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,KAChB,sBAAsB,GAAG,SAAS,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAC;IACtF,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,EAAE,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB9B;AAqLD;;;;;;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,CA8CxC;AA+hCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
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;AAmC7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAEb,QAAQ,EACR,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AAEjF,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE;;;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,GACjB,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,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAmVD;;;;;;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;AA+hCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
@@ -3,13 +3,15 @@
3
3
  * Transforms to Message[] only at the LLM call boundary.
4
4
  */
5
5
  import { EventStream } from "@caupulican/pi-ai/event-stream";
6
- import { streamSimple } from "@caupulican/pi-ai/stream";
7
6
  import { formatToolRepairStandingRule, REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE, } from "@caupulican/pi-ai/tool-repair-registry";
8
7
  import { ToolArgumentValidationError, validateToolArguments, } from "@caupulican/pi-ai/validation";
9
- import { assessToolFailure, clearToolFailure, createRepeatedToolFailureResult, createToolFailureMemoryTracker, createToolFailureRecoveryExhaustedResult, createToolFailureResult, getUnresolvedToolFailure, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
8
+ import { startPlannedAgentProviderRequest } from "./provider-request-planner.js";
9
+ import { assessToolFailure, clearToolFailure, createRepeatedToolFailureResult, createToolFailureMemoryTracker, createToolFailureRecoveryExhaustedResult, createToolFailureResult, getUnresolvedToolFailure, normalizeToolSignature, rememberToolFailure, toolFailureCorrection, } from "./tool-failure-memory.js";
10
10
  import { TOOL_FAILURE_RECOVERY_ACCOUNTING_WAVE_SIZE, ToolFailureRecoveryGate, } from "./tool-failure-recovery-gate.js";
11
+ import { appendMandatoryToolFailureDeliveryPrompt } from "./tool-failure-recovery-protocol.js";
11
12
  import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
12
13
  import { createEmptyUsage } from "./usage.js";
14
+ export { resolveRequestPreflightMaxTokens } from "./provider-request-planner.js";
13
15
  /**
14
16
  * Start an agent loop with a new prompt message.
15
17
  * The prompt is added to the context and events are emitted for it.
@@ -89,6 +91,24 @@ function createLoopFailureMessage(error, config, aborted) {
89
91
  timestamp: Date.now(),
90
92
  };
91
93
  }
94
+ function createMandatoryRecoveryDeliveryFallback(halt, config) {
95
+ const diagnostic = halt.record.diagnostic ?? halt.diagnostic;
96
+ return {
97
+ role: "assistant",
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: `Tool recovery stopped for ${halt.record.tool}: ${diagnostic} Required recovery: ${halt.record.correction}`,
102
+ },
103
+ ],
104
+ api: config.model.api,
105
+ provider: config.model.provider,
106
+ model: config.model.id,
107
+ usage: createEmptyUsage(),
108
+ stopReason: "stop",
109
+ timestamp: Date.now(),
110
+ };
111
+ }
92
112
  function createAgentStream() {
93
113
  return new EventStream((event) => event.type === "agent_end", (event) => (event.type === "agent_end" ? event.messages : []));
94
114
  }
@@ -144,31 +164,6 @@ function repeatsSuccessfulTextProtocolBatch(incomingSignature, toolCalls, previo
144
164
  return phase === "edited" && toolCall.name === "edit" && previousCall.name === "edit";
145
165
  });
146
166
  }
147
- /**
148
- * Apply one request-local preflight without mutating persistent loop configuration.
149
- * Shared with isolated tool-free provider calls so every transport boundary has identical
150
- * validation and non-widening semantics.
151
- */
152
- export async function resolveRequestPreflightMaxTokens(options) {
153
- if (!options.requestPreflight)
154
- return options.maxTokens;
155
- if (options.maxTokens !== undefined && (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {
156
- throw new TypeError("request maxTokens must be a positive safe integer");
157
- }
158
- const preflight = await options.requestPreflight({ model: options.model, context: options.context, maxTokens: options.maxTokens }, options.signal);
159
- if (preflight?.maxTokens === undefined)
160
- return options.maxTokens;
161
- if (!Number.isSafeInteger(preflight.maxTokens) || preflight.maxTokens <= 0) {
162
- throw new TypeError("requestPreflight.maxTokens must be a positive safe integer");
163
- }
164
- const ceilings = [preflight.maxTokens];
165
- if (options.maxTokens !== undefined)
166
- ceilings.push(options.maxTokens);
167
- if (Number.isSafeInteger(options.model.maxTokens) && options.model.maxTokens > 0) {
168
- ceilings.push(options.model.maxTokens);
169
- }
170
- return Math.min(...ceilings);
171
- }
172
167
  /**
173
168
  * Main loop logic shared by agentLoop and agentLoopContinue.
174
169
  */
@@ -191,6 +186,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
191
186
  const repairTeachTracker = new Map();
192
187
  let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
193
188
  const toolFailureRecoveryGate = new ToolFailureRecoveryGate();
189
+ let mandatoryRecoveryDeliveryPending = false;
194
190
  let lastSuccessfulTextProtocolBatch;
195
191
  // Check for steering messages at start (user may have typed while waiting)
196
192
  let pendingMessages = (await config.getSteeringMessages?.()) || [];
@@ -216,8 +212,26 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
216
212
  }
217
213
  pendingMessages = [];
218
214
  }
219
- // Stream assistant response
220
- const message = await streamAssistantResponse(currentContext, config, signal, emit, streamFn);
215
+ // Recovery exhaustion receives exactly one provider turn without tools so the mandatory
216
+ // diagnostic can reach the user instead of terminating on an undelivered tool result.
217
+ const recoveryDeliveryTurn = mandatoryRecoveryDeliveryPending;
218
+ const recoveryDeliveryHalt = recoveryDeliveryTurn ? toolFailureRecoveryGate.getHalt() : undefined;
219
+ if (recoveryDeliveryTurn && !recoveryDeliveryHalt) {
220
+ throw new Error("Mandatory recovery delivery started without a recovery halt");
221
+ }
222
+ const responseContext = recoveryDeliveryHalt
223
+ ? {
224
+ ...currentContext,
225
+ systemPrompt: appendMandatoryToolFailureDeliveryPrompt(currentContext.systemPrompt, {
226
+ tool: recoveryDeliveryHalt.record.tool,
227
+ failureCode: recoveryDeliveryHalt.record.failureCode,
228
+ diagnostic: recoveryDeliveryHalt.record.diagnostic ?? recoveryDeliveryHalt.diagnostic,
229
+ requiredAction: recoveryDeliveryHalt.record.correction,
230
+ }),
231
+ tools: [],
232
+ }
233
+ : currentContext;
234
+ const message = await streamAssistantResponse(responseContext, config, signal, emit, streamFn);
221
235
  newMessages.push(message);
222
236
  if (message.stopReason === "error" || message.stopReason === "aborted") {
223
237
  await emit({ type: "turn_end", message, toolResults: [] });
@@ -227,6 +241,30 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
227
241
  // Check for tool calls
228
242
  const toolCalls = message.content.filter((c) => c.type === "toolCall");
229
243
  const toolResults = [];
244
+ if (recoveryDeliveryTurn) {
245
+ if (!recoveryDeliveryHalt) {
246
+ throw new Error("Mandatory recovery delivery continued without a recovery halt");
247
+ }
248
+ if (toolCalls.length > 0) {
249
+ toolResults.push(...(await rejectRecoveryDeliveryToolCalls(toolCalls, recoveryDeliveryHalt, emit)));
250
+ for (const result of toolResults) {
251
+ currentContext.messages.push(result);
252
+ newMessages.push(result);
253
+ }
254
+ }
255
+ await emit({ type: "turn_end", message, toolResults });
256
+ if (toolCalls.length > 0) {
257
+ const fallback = createMandatoryRecoveryDeliveryFallback(recoveryDeliveryHalt, config);
258
+ currentContext.messages.push(fallback);
259
+ newMessages.push(fallback);
260
+ await emit({ type: "turn_start" });
261
+ await emit({ type: "message_start", message: fallback });
262
+ await emit({ type: "message_end", message: fallback });
263
+ await emit({ type: "turn_end", message: fallback, toolResults: [] });
264
+ }
265
+ await emit({ type: "agent_end", messages: newMessages });
266
+ return;
267
+ }
230
268
  hasMoreToolCalls = false;
231
269
  if (toolCalls.length > 0) {
232
270
  const textProtocolBatch = toolCalls.every((toolCall) => toolCall.source === "text-protocol");
@@ -237,7 +275,13 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
237
275
  : undefined;
238
276
  const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, toolFailureRecoveryGate, previousSuccessfulTextProtocolResults, signal, emit);
239
277
  toolResults.push(...executedToolBatch.messages);
240
- hasMoreToolCalls = !executedToolBatch.terminate;
278
+ if (toolFailureRecoveryGate.isHalted()) {
279
+ mandatoryRecoveryDeliveryPending = true;
280
+ hasMoreToolCalls = true;
281
+ }
282
+ else {
283
+ hasMoreToolCalls = !executedToolBatch.terminate;
284
+ }
241
285
  for (const result of toolResults) {
242
286
  currentContext.messages.push(result);
243
287
  newMessages.push(result);
@@ -260,7 +304,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
260
304
  }
261
305
  await emit({ type: "turn_end", message, toolResults });
262
306
  // Runaway-loop backstop (cost guard): detect a model stuck repeating one action.
263
- if (stallLimit > 0 && toolCalls.length > 0) {
307
+ if (!mandatoryRecoveryDeliveryPending && stallLimit > 0 && toolCalls.length > 0) {
264
308
  const signature = normalizeToolSignature(toolCalls.map((c) => [c.name, c.arguments ?? null]));
265
309
  stallWindow.push(signature);
266
310
  if (stallWindow.length > stallLimit * STALL_WINDOW_PERIODS)
@@ -290,12 +334,13 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
290
334
  reasoning: nextTurnSnapshot.thinkingLevel ?? config.reasoning,
291
335
  };
292
336
  }
293
- if (await config.shouldStopAfterTurn?.({
294
- message,
295
- toolResults,
296
- context: currentContext,
297
- newMessages,
298
- })) {
337
+ if (!mandatoryRecoveryDeliveryPending &&
338
+ (await config.shouldStopAfterTurn?.({
339
+ message,
340
+ toolResults,
341
+ context: currentContext,
342
+ newMessages,
343
+ }))) {
299
344
  await emit({ type: "agent_end", messages: newMessages });
300
345
  return;
301
346
  }
@@ -321,46 +366,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
321
366
  * and request preflight immediately before transport.
322
367
  */
323
368
  export async function startAgentProviderRequest(context, config, signal, streamFn) {
324
- // Failed protocol turns never reach host transforms or provider conversion. Their bounded,
325
- // unresolved state is carried separately in the system prompt until the same operation succeeds.
326
- const sanitized = sanitizeToolFailureContext(context.messages, context.systemPrompt);
327
- let messages = sanitized.messages;
328
- if (config.transformContext) {
329
- messages = await config.transformContext(messages, signal);
330
- }
331
- // Convert to LLM-compatible messages (AgentMessage[] → Message[])
332
- const llmMessages = await config.convertToLlm(messages);
333
- // Build LLM context
334
- const llmContext = {
335
- systemPrompt: sanitized.systemPrompt,
336
- messages: llmMessages,
337
- tools: context.tools,
338
- };
339
- const streamFunction = streamFn || streamSimple;
340
- const requestMaxTokens = await resolveRequestPreflightMaxTokens({
341
- requestPreflight: config.requestPreflight,
342
- model: config.model,
343
- context: llmContext,
344
- maxTokens: config.maxTokens,
345
- signal,
346
- });
347
- // Resolve credentials only after the request-local authority/budget gate accepts the request.
348
- // This prevents an already-exhausted background lane from refreshing OAuth/SSO credentials.
349
- const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
350
- const requestReasoning = config.resolveRequestReasoning
351
- ? config.resolveRequestReasoning(config.reasoning, {
352
- model: config.model,
353
- context: llmContext,
354
- maxTokens: requestMaxTokens,
355
- })
356
- : config.reasoning;
357
- return await streamFunction(config.model, llmContext, {
358
- ...config,
359
- apiKey: resolvedApiKey,
360
- maxTokens: requestMaxTokens,
361
- reasoning: requestReasoning,
362
- signal,
363
- });
369
+ return startPlannedAgentProviderRequest(context, config, signal, streamFn);
364
370
  }
365
371
  /**
366
372
  * Stream an assistant response from the LLM.
@@ -1126,4 +1132,20 @@ async function emitToolResultMessage(toolResultMessage, emit) {
1126
1132
  await emit({ type: "message_start", message: toolResultMessage });
1127
1133
  await emit({ type: "message_end", message: toolResultMessage });
1128
1134
  }
1135
+ async function rejectRecoveryDeliveryToolCalls(toolCalls, halt, emit) {
1136
+ const messages = [];
1137
+ for (const toolCall of toolCalls) {
1138
+ await emitToolExecutionStart(toolCall, emit);
1139
+ const finalized = {
1140
+ toolCall,
1141
+ result: createToolFailureRecoveryExhaustedResult(halt.record, "Tool execution is disabled during the mandatory recovery delivery turn."),
1142
+ isError: true,
1143
+ };
1144
+ await emitToolExecutionEnd(finalized, emit);
1145
+ const message = createToolResultMessage(finalized);
1146
+ await emitToolResultMessage(message, emit);
1147
+ messages.push(message);
1148
+ }
1149
+ return messages;
1150
+ }
1129
1151
  //# sourceMappingURL=agent-loop.js.map