@oh-my-pi/pi-agent-core 17.1.5 → 17.1.7
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/CHANGELOG.md +16 -0
- package/dist/types/agent.d.ts +21 -1
- package/dist/types/types.d.ts +63 -7
- package/package.json +7 -7
- package/src/agent-loop.ts +393 -204
- package/src/agent.ts +82 -1
- package/src/types.ts +73 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.7] - 2026-07-27
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `beforeToolCall` now runs during arg-prep in a pre-dispatch prepare phase — on the streamed path before the assistant message's `message_start`/`message_end` are emitted, and always ahead of concurrency resolution, `tool_execution_start`, telemetry span start, and `tool.execute` — instead of inside the already-scheduled execution slot. It receives the resolved `tool` in its context and may return `args` to replace the call's arguments; a replacement is revalidated against the tool schema, written back to the assistant message's tool-call block, and re-resolves argument-dependent interruptibility, making it the single source of truth for history, persistence, provider replay, scheduling, execution events, and `tool.execute`. Argument validation moved into the same prepare phase, so functional `concurrency` resolvers now see validated (and possibly revised) arguments rather than raw pre-validation ones. The hook now receives the run's request abort signal rather than the per-tool signal.
|
|
10
|
+
|
|
11
|
+
## [17.1.6] - 2026-07-27
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added a pre-model-call gate: `AgentLoopConfig.beforeModelCall` receives the finalized provider context and run abort signal, and may return `{ stop: true, reason? }` to end the run before the provider is called, so a host can refuse a request it has decided not to pay for (prompt no longer fits, budget boundary crossed, session should hand off). `Agent.setBeforeModelCall` installs the host callback; `Agent.addBeforeModelCall` registers an additional one without displacing it and returns a disposer. A gate-stopped run retains pending soft tool reminders/escalations and an unserved hard tool choice for the next admitted request; deferred choices are revalidated against active tools and cleared with queued session state ([#6543](https://github.com/can1357/oh-my-pi/pull/6543) by [@paralin](https://github.com/paralin)).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Input message events (prompt, steering, soft reminders) are now emitted once provider-context preparation succeeds, so a pre-model gate can veto the request before any turn opens; gate-stopped and failed runs still commit their accepted inputs ([#6543](https://github.com/can1357/oh-my-pi/pull/6543) by [@paralin](https://github.com/paralin)).
|
|
20
|
+
|
|
5
21
|
## [17.1.5] - 2026-07-27
|
|
6
22
|
|
|
7
23
|
### Fixed
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ApiKey, type AssistantMessage, type AssistantMessageEvent, type Co
|
|
|
2
2
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
3
3
|
import type { HarmonyAuditEvent } from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
4
4
|
import type { AppendOnlyContextManager } from "./append-only-context.js";
|
|
5
|
-
import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AgentTurnEndContext, AsideMessage, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types.js";
|
|
5
|
+
import type { AgentBeforeModelCall, AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, AgentTurnEndContext, AsideMessage, StreamFn, ToolCallContext, ToolChoiceDirective } from "./types.js";
|
|
6
6
|
export declare class AgentBusyError extends Error {
|
|
7
7
|
constructor(message?: string);
|
|
8
8
|
}
|
|
@@ -159,6 +159,8 @@ export interface AgentOptions {
|
|
|
159
159
|
abortOnFabricatedToolResult?: boolean;
|
|
160
160
|
/** Dynamic tool-choice directive (hard {@link ToolChoice} or {@link SoftToolRequirement}), resolved once per turn. */
|
|
161
161
|
getToolChoice?: () => ToolChoiceDirective | undefined;
|
|
162
|
+
/** Reject a deferred hard choice when its named tool is no longer active. */
|
|
163
|
+
onToolChoiceUnavailable?: () => void;
|
|
162
164
|
/**
|
|
163
165
|
* Cursor exec handlers for local tool execution.
|
|
164
166
|
*/
|
|
@@ -374,6 +376,19 @@ export declare class Agent {
|
|
|
374
376
|
setAssistantMessageEventInterceptor(fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined): void;
|
|
375
377
|
setOnBeforeYield(fn: (() => Promise<void> | void) | undefined): void;
|
|
376
378
|
setOnTurnEnd(fn: ((messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void) | undefined): void;
|
|
379
|
+
/**
|
|
380
|
+
* Install or replace the host pre-model-call gate; pass `undefined` to
|
|
381
|
+
* remove it. Gates are sampled when a run starts: installing the first
|
|
382
|
+
* gate while a run is in flight takes effect on the next run.
|
|
383
|
+
*/
|
|
384
|
+
setBeforeModelCall(fn: AgentBeforeModelCall | undefined): void;
|
|
385
|
+
/**
|
|
386
|
+
* Add a pre-model callback without replacing callbacks owned by the host.
|
|
387
|
+
* Returns a disposer that removes only this callback. Like
|
|
388
|
+
* {@link setBeforeModelCall}, the first gate installed while a run is in
|
|
389
|
+
* flight takes effect on the next run.
|
|
390
|
+
*/
|
|
391
|
+
addBeforeModelCall(fn: AgentBeforeModelCall): () => void;
|
|
377
392
|
/**
|
|
378
393
|
* Provide a source of non-interrupting "aside" messages (e.g. background-job
|
|
379
394
|
* completions, late LSP diagnostics) drained at each step boundary. Never
|
|
@@ -408,6 +423,11 @@ export declare class Agent {
|
|
|
408
423
|
followUp(m: AgentMessage): void;
|
|
409
424
|
clearSteeringQueue(): void;
|
|
410
425
|
clearFollowUpQueue(): void;
|
|
426
|
+
/**
|
|
427
|
+
* Drop tool-directive state retained across a gate-stopped run: the
|
|
428
|
+
* deferred hard choice and the soft-requirement lifecycle.
|
|
429
|
+
*/
|
|
430
|
+
clearDeferredToolDirectives(): void;
|
|
411
431
|
clearAllQueues(): void;
|
|
412
432
|
hasQueuedMessages(): boolean;
|
|
413
433
|
/** Non-consuming view of the pending steering queue (insertion order, newest
|
package/dist/types/types.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export interface AgentTurnEndContext {
|
|
|
21
21
|
/** True when the current tool-loop batch is continuing without yielding to post-turn steering. */
|
|
22
22
|
willContinue: boolean;
|
|
23
23
|
}
|
|
24
|
+
export interface AgentPreModelCallStop {
|
|
25
|
+
/** Stop the agent loop before sending the next provider request. */
|
|
26
|
+
stop: true;
|
|
27
|
+
/** Optional owner-facing reason, logged by the loop when it stops. */
|
|
28
|
+
reason?: string;
|
|
29
|
+
}
|
|
30
|
+
export type AgentPreModelCallResult = AgentPreModelCallStop | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* A pre-model-call gate. Return {@link AgentPreModelCallStop} to refuse the
|
|
33
|
+
* request, or nothing to proceed; the signal aborts with the run.
|
|
34
|
+
*/
|
|
35
|
+
export type AgentBeforeModelCall = (context: Context, signal?: AbortSignal) => AgentPreModelCallResult | void | Promise<AgentPreModelCallResult | void>;
|
|
24
36
|
/**
|
|
25
37
|
* A soft tool requirement: the host wants `toolName` called before the loop
|
|
26
38
|
* runs other tools or yields, but WITHOUT paying the forced-`toolChoice` cost
|
|
@@ -61,6 +73,12 @@ export interface SoftToolRequirement {
|
|
|
61
73
|
* (applied verbatim) or a {@link SoftToolRequirement} (remind-then-escalate).
|
|
62
74
|
*/
|
|
63
75
|
export type ToolChoiceDirective = ToolChoice | SoftToolRequirement;
|
|
76
|
+
/** Mutable soft-requirement lifecycle retained across stopped agent runs. */
|
|
77
|
+
export interface SoftToolRequirementState {
|
|
78
|
+
id?: string;
|
|
79
|
+
forcedToolChoice?: ToolChoice;
|
|
80
|
+
escalations: number;
|
|
81
|
+
}
|
|
64
82
|
/** True when a {@link ToolChoiceDirective} is a soft requirement, not a hard choice. */
|
|
65
83
|
export declare function isSoftToolRequirement(directive: ToolChoiceDirective | undefined): directive is SoftToolRequirement;
|
|
66
84
|
/** Source category for a queued steering interrupt observed without consuming the queue. */
|
|
@@ -230,8 +248,21 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
230
248
|
/**
|
|
231
249
|
* Refreshes prompt/tool context from live session state before each model call.
|
|
232
250
|
* Use this when tool availability or the system prompt can change mid-turn.
|
|
251
|
+
*
|
|
252
|
+
* Runs after pending messages are folded in and before provider conversion.
|
|
253
|
+
* Mutate the agent context here; use `beforeModelCall` to inspect the
|
|
254
|
+
* provider-bound context.
|
|
233
255
|
*/
|
|
234
256
|
syncContextBeforeModelCall?: (context: AgentContext) => void | Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Asked after the complete provider context has been built, including
|
|
259
|
+
* message conversion, provider transforms, normalized tools, and owned
|
|
260
|
+
* dialect prompt injection. Returning {@link AgentPreModelCallStop} ends
|
|
261
|
+
* the stream without emitting `turn_start`, so no turn is left open and no
|
|
262
|
+
* request is billed. Return nothing to proceed. The signal aborts when
|
|
263
|
+
* the run is canceled or its deadline expires.
|
|
264
|
+
*/
|
|
265
|
+
beforeModelCall?: AgentBeforeModelCall;
|
|
235
266
|
/**
|
|
236
267
|
* Optional transform applied to tool call arguments before execution.
|
|
237
268
|
* Use for deobfuscating secrets or rewriting arguments.
|
|
@@ -304,6 +335,17 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
304
335
|
* to the static `toolChoice`.
|
|
305
336
|
*/
|
|
306
337
|
getToolChoice?: () => ToolChoiceDirective | undefined;
|
|
338
|
+
/**
|
|
339
|
+
* Soft-requirement lifecycle retained by the host when a pre-model-call
|
|
340
|
+
* gate stops a run before its pending reminder or escalation is served.
|
|
341
|
+
*/
|
|
342
|
+
softToolRequirementState?: SoftToolRequirementState;
|
|
343
|
+
/**
|
|
344
|
+
* Notifies the host that the pre-model-call gate stopped the run after a
|
|
345
|
+
* hard tool choice was obtained from {@link getToolChoice} but before it
|
|
346
|
+
* was served, so the host can retain it for the next admitted request.
|
|
347
|
+
*/
|
|
348
|
+
onToolChoiceRejected?: () => void;
|
|
307
349
|
/**
|
|
308
350
|
* Dynamic reasoning effort override, resolved per LLM call.
|
|
309
351
|
* When set and returns a value, overrides the static `reasoning` captured
|
|
@@ -348,15 +390,22 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
348
390
|
*/
|
|
349
391
|
getCwd?: () => string | undefined;
|
|
350
392
|
/**
|
|
351
|
-
* Called
|
|
393
|
+
* Called once per tool call after argument validation, in call order, before
|
|
394
|
+
* the call is scheduled — ahead of concurrency resolution,
|
|
395
|
+
* `tool_execution_start`, and `tool.execute`. On the streamed path it runs
|
|
396
|
+
* before the assistant message's `message_start`/`message_end` are emitted.
|
|
352
397
|
*
|
|
353
398
|
* Return `{ block: true }` to prevent execution. The loop emits an error tool
|
|
354
399
|
* result instead (using `reason` as the error text, or a default if omitted).
|
|
355
400
|
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
401
|
+
* Return `{ args }` to replace the arguments the call runs with. The
|
|
402
|
+
* replacement is revalidated against the tool schema and written back to the
|
|
403
|
+
* tool-call block, making it the single source of truth: history, execution
|
|
404
|
+
* events, persistence, provider replay, concurrency scheduling, and
|
|
405
|
+
* `tool.execute` all see the revised arguments. Mutating `context.args` in
|
|
406
|
+
* place also survives into execution, but a returned `args` object wins.
|
|
358
407
|
*
|
|
359
|
-
* The hook receives the
|
|
408
|
+
* The hook receives the run's request abort signal and is responsible for
|
|
360
409
|
* honoring it. Throwing surfaces as a tool-error result and does not abort the
|
|
361
410
|
* rest of the batch.
|
|
362
411
|
*/
|
|
@@ -435,12 +484,16 @@ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
|
|
|
435
484
|
* Set `block: true` to prevent the tool from executing. The loop emits an error tool
|
|
436
485
|
* result instead, using `reason` as the error text (or a default if omitted).
|
|
437
486
|
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
487
|
+
* Set `args` to replace the tool-call arguments. The replacement is revalidated
|
|
488
|
+
* against the tool schema (a failure surfaces as a validation-error tool result),
|
|
489
|
+
* written back to the tool-call block on the assistant message, and seen by
|
|
490
|
+
* history, scheduling, execution events, and `tool.execute` alike. It is
|
|
491
|
+
* ignored when `block` is true.
|
|
440
492
|
*/
|
|
441
493
|
export interface BeforeToolCallResult {
|
|
442
494
|
block?: boolean;
|
|
443
495
|
reason?: string;
|
|
496
|
+
args?: Record<string, unknown>;
|
|
444
497
|
}
|
|
445
498
|
/**
|
|
446
499
|
* Partial override returned from `afterToolCall`.
|
|
@@ -466,9 +519,12 @@ export interface BeforeToolCallContext {
|
|
|
466
519
|
assistantMessage: AssistantMessage;
|
|
467
520
|
/** The raw tool call block from `assistantMessage.content`. */
|
|
468
521
|
toolCall: AgentToolCall;
|
|
522
|
+
/** The resolved tool the call dispatches to. */
|
|
523
|
+
tool: AgentTool<any>;
|
|
469
524
|
/**
|
|
470
525
|
* Validated tool arguments. The same reference is forwarded to `tool.execute`
|
|
471
|
-
* (after any `transformToolCallArguments` pass), so in-place mutations stick
|
|
526
|
+
* (after any `transformToolCallArguments` pass), so in-place mutations stick;
|
|
527
|
+
* a returned `BeforeToolCallResult.args` replaces them entirely.
|
|
472
528
|
*/
|
|
473
529
|
args: Record<string, unknown>;
|
|
474
530
|
/** Current agent context at the time the tool call is prepared. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.7",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "17.1.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "17.1.
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
42
|
-
"@oh-my-pi/pi-wire": "17.1.
|
|
43
|
-
"@oh-my-pi/snapcompact": "17.1.
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.1.7",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.1.7",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.1.7",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.1.7",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.1.7",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.1.7",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|