@linxiraos/pi-agent-core 1.0.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/CHANGELOG.md +1263 -0
- package/README.md +473 -0
- package/dist/types/agent-loop.d.ts +137 -0
- package/dist/types/agent.d.ts +465 -0
- package/dist/types/append-only-context.d.ts +131 -0
- package/dist/types/compaction/branch-summarization.d.ts +101 -0
- package/dist/types/compaction/compaction-v2-streaming.d.ts +85 -0
- package/dist/types/compaction/compaction.d.ts +307 -0
- package/dist/types/compaction/entries.d.ts +111 -0
- package/dist/types/compaction/errors.d.ts +38 -0
- package/dist/types/compaction/index.d.ts +13 -0
- package/dist/types/compaction/message-cache.d.ts +23 -0
- package/dist/types/compaction/messages.d.ts +79 -0
- package/dist/types/compaction/openai.d.ts +117 -0
- package/dist/types/compaction/pruning.d.ts +105 -0
- package/dist/types/compaction/shake.d.ts +97 -0
- package/dist/types/compaction/tool-protection.d.ts +19 -0
- package/dist/types/compaction/utils.d.ts +61 -0
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/pause.d.ts +46 -0
- package/dist/types/proxy.d.ts +91 -0
- package/dist/types/replay-policy.d.ts +5 -0
- package/dist/types/run-collector.d.ts +196 -0
- package/dist/types/telemetry.d.ts +608 -0
- package/dist/types/thinking.d.ts +18 -0
- package/dist/types/tokenizer.d.ts +2 -0
- package/dist/types/types.d.ts +781 -0
- package/dist/types/utils/yield.d.ts +71 -0
- package/package.json +79 -0
- package/src/agent-loop.ts +2926 -0
- package/src/agent.ts +1748 -0
- package/src/append-only-context.ts +348 -0
- package/src/compaction/branch-summarization.ts +369 -0
- package/src/compaction/compaction-v2-streaming.ts +846 -0
- package/src/compaction/compaction.ts +1733 -0
- package/src/compaction/entries.ts +141 -0
- package/src/compaction/errors.ts +47 -0
- package/src/compaction/index.ts +14 -0
- package/src/compaction/message-cache.ts +92 -0
- package/src/compaction/messages.ts +241 -0
- package/src/compaction/openai.ts +992 -0
- package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
- package/src/compaction/prompts/branch-summary-context.md +5 -0
- package/src/compaction/prompts/branch-summary-preamble.md +2 -0
- package/src/compaction/prompts/branch-summary.md +30 -0
- package/src/compaction/prompts/compaction-short-summary.md +9 -0
- package/src/compaction/prompts/compaction-summary-context.md +5 -0
- package/src/compaction/prompts/compaction-summary.md +38 -0
- package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
- package/src/compaction/prompts/compaction-update-summary.md +45 -0
- package/src/compaction/prompts/context-window-truncated-output.md +1 -0
- package/src/compaction/prompts/file-operations.md +5 -0
- package/src/compaction/prompts/handoff-document.md +49 -0
- package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
- package/src/compaction/prompts/summarization-system.md +3 -0
- package/src/compaction/pruning.ts +427 -0
- package/src/compaction/shake.ts +446 -0
- package/src/compaction/tool-protection.ts +65 -0
- package/src/compaction/utils.ts +343 -0
- package/src/compaction.ts +1 -0
- package/src/index.ts +26 -0
- package/src/pause.ts +107 -0
- package/src/proxy.ts +391 -0
- package/src/replay-policy.ts +13 -0
- package/src/run-collector.ts +631 -0
- package/src/telemetry.ts +2078 -0
- package/src/thinking.ts +20 -0
- package/src/tokenizer.ts +27 -0
- package/src/types.ts +875 -0
- package/src/utils/yield.ts +183 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApiKey,
|
|
3
|
+
AssistantMessage,
|
|
4
|
+
AssistantMessageEvent,
|
|
5
|
+
AssistantMessageEventStream,
|
|
6
|
+
Context,
|
|
7
|
+
Effort,
|
|
8
|
+
ImageContent,
|
|
9
|
+
Message,
|
|
10
|
+
Model,
|
|
11
|
+
ServiceTier,
|
|
12
|
+
SimpleStreamOptions,
|
|
13
|
+
Static,
|
|
14
|
+
streamSimple,
|
|
15
|
+
TextContent,
|
|
16
|
+
Tool,
|
|
17
|
+
ToolCallProviderMetadata,
|
|
18
|
+
ToolChoice,
|
|
19
|
+
ToolResultMessage,
|
|
20
|
+
ToolResultProviderMetadata,
|
|
21
|
+
TSchema,
|
|
22
|
+
} from "@linxiraos/pi-ai";
|
|
23
|
+
import type { Dialect } from "@linxiraos/pi-ai/dialect";
|
|
24
|
+
import type { HarmonyAuditEvent } from "@linxiraos/pi-ai/utils/harmony-leak";
|
|
25
|
+
import type { AppendOnlyContextManager } from "./append-only-context";
|
|
26
|
+
import type { AgentRunCoverage, AgentRunSummary } from "./run-collector";
|
|
27
|
+
import type { AgentTelemetryConfig } from "./telemetry";
|
|
28
|
+
|
|
29
|
+
/** Stream function - can return sync or Promise for async config lookup */
|
|
30
|
+
export type StreamFn = (
|
|
31
|
+
...args: Parameters<typeof streamSimple>
|
|
32
|
+
) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
|
|
33
|
+
|
|
34
|
+
/** Called once an aside has been inserted into the agent's live context. */
|
|
35
|
+
export const ASIDE_MESSAGE_COMMIT = Symbol("aside-message-commit");
|
|
36
|
+
/** Called when an aside was drained but the agent loop ended before inserting it. */
|
|
37
|
+
export const ASIDE_MESSAGE_DISCARD = Symbol("aside-message-discard");
|
|
38
|
+
|
|
39
|
+
export type CommittableAsideMessage = AgentMessage & {
|
|
40
|
+
[ASIDE_MESSAGE_COMMIT]?: () => void;
|
|
41
|
+
[ASIDE_MESSAGE_DISCARD]?: (error: Error) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* An aside entry: a ready {@link AgentMessage}, or a sync thunk evaluated at
|
|
46
|
+
* injection time that returns the message to inject or `null` to skip it. Thunks
|
|
47
|
+
* let the producer make the final inject-or-drop decision against current state
|
|
48
|
+
* (e.g. dropping late diagnostics a newer edit superseded).
|
|
49
|
+
*/
|
|
50
|
+
export type AsideMessage = CommittableAsideMessage | (() => CommittableAsideMessage | null);
|
|
51
|
+
|
|
52
|
+
export interface AgentTurnEndContext {
|
|
53
|
+
/** Assistant/user message that just completed this turn boundary. */
|
|
54
|
+
message: AgentMessage;
|
|
55
|
+
/** Tool results produced by this turn, already paired with `message` in the live context. */
|
|
56
|
+
toolResults: ToolResultMessage[];
|
|
57
|
+
/** True when the current tool-loop batch is continuing without yielding to post-turn steering. */
|
|
58
|
+
willContinue: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AgentPreModelCallStop {
|
|
62
|
+
/** Stop the agent loop before sending the next provider request. */
|
|
63
|
+
stop: true;
|
|
64
|
+
/** Optional owner-facing reason, logged by the loop when it stops. */
|
|
65
|
+
reason?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type AgentPreModelCallResult = AgentPreModelCallStop | undefined;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A pre-model-call gate. Return {@link AgentPreModelCallStop} to refuse the
|
|
72
|
+
* request, or nothing to proceed; the signal aborts with the run.
|
|
73
|
+
*/
|
|
74
|
+
export type AgentBeforeModelCall = (
|
|
75
|
+
context: Context,
|
|
76
|
+
signal?: AbortSignal,
|
|
77
|
+
) => AgentPreModelCallResult | void | Promise<AgentPreModelCallResult | void>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A soft tool requirement: the host wants `toolName` called before the loop
|
|
81
|
+
* runs other tools or yields, but WITHOUT paying the forced-`toolChoice` cost
|
|
82
|
+
* up front (changing `tool_choice` invalidates the provider message cache).
|
|
83
|
+
* Returned from {@link AgentLoopConfig.getToolChoice} in place of a hard
|
|
84
|
+
* {@link ToolChoice}: the loop injects `reminder` once when a new `id` becomes
|
|
85
|
+
* active, runs with `toolChoice` unchanged, and escalates to a one-turn forced
|
|
86
|
+
* choice only if the model fails to call `toolName`. Auto-clears when the host
|
|
87
|
+
* stops returning it or `toolName` is no longer an active tool.
|
|
88
|
+
*/
|
|
89
|
+
export interface SoftToolRequirement {
|
|
90
|
+
/** Discriminates a soft requirement from a hard {@link ToolChoice}. */
|
|
91
|
+
soft: true;
|
|
92
|
+
/**
|
|
93
|
+
* Stable id of the *current* requirement. The loop injects `reminder` when
|
|
94
|
+
* this id first becomes active and again whenever it changes (e.g. one
|
|
95
|
+
* stacked preview resolves and the next becomes the head), but never
|
|
96
|
+
* re-injects for an unchanged id across turns.
|
|
97
|
+
*/
|
|
98
|
+
id: string;
|
|
99
|
+
/** Tool that must be called before the loop runs other tools or yields. */
|
|
100
|
+
toolName: string;
|
|
101
|
+
/**
|
|
102
|
+
* Per-call compliance check: a turn satisfies the requirement only when every
|
|
103
|
+
* tool call passes. Defaults to `name === toolName`. Lets a host demand a
|
|
104
|
+
* specific invocation shape (e.g. `write` targeting a virtual device path)
|
|
105
|
+
* instead of any call to `toolName`. Escalation still forces `toolName`.
|
|
106
|
+
*/
|
|
107
|
+
satisfies?(toolCall: { name: string; arguments?: Record<string, unknown> }): boolean;
|
|
108
|
+
/** Host-owned reminder messages, injected once per `id` activation. */
|
|
109
|
+
reminder: AgentMessage[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A per-turn tool-choice directive: either a hard provider {@link ToolChoice}
|
|
114
|
+
* (applied verbatim) or a {@link SoftToolRequirement} (remind-then-escalate).
|
|
115
|
+
*/
|
|
116
|
+
export type ToolChoiceDirective = ToolChoice | SoftToolRequirement;
|
|
117
|
+
|
|
118
|
+
/** Mutable soft-requirement lifecycle retained across stopped agent runs. */
|
|
119
|
+
export interface SoftToolRequirementState {
|
|
120
|
+
id?: string;
|
|
121
|
+
forcedToolChoice?: ToolChoice;
|
|
122
|
+
escalations: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** True when a {@link ToolChoiceDirective} is a soft requirement, not a hard choice. */
|
|
126
|
+
export function isSoftToolRequirement(directive: ToolChoiceDirective | undefined): directive is SoftToolRequirement {
|
|
127
|
+
return typeof directive === "object" && directive !== null && (directive as SoftToolRequirement).soft === true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Source category for a queued steering interrupt observed without consuming the queue.
|
|
132
|
+
* Distinguishes real-user, agent-authored, system/advisor, and unknown steering.
|
|
133
|
+
*/
|
|
134
|
+
export type SteeringInterruptSource = "user" | "agent" | "system" | "unknown";
|
|
135
|
+
|
|
136
|
+
/** Non-consuming summary of whether queued steering should interrupt a tool batch. */
|
|
137
|
+
export interface SteeringQueueState {
|
|
138
|
+
/** True when at least one steering message is queued. */
|
|
139
|
+
queued: boolean;
|
|
140
|
+
/** Best-effort origin used only to word synthetic skipped-tool results. */
|
|
141
|
+
source?: SteeringInterruptSource;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Configuration for the agent loop.
|
|
146
|
+
*/
|
|
147
|
+
export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
148
|
+
model: Model;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* When to interrupt tool execution for steering messages.
|
|
152
|
+
* - "immediate" = check after each tool call (default)
|
|
153
|
+
* - "wait" = defer steering until the current turn completes
|
|
154
|
+
*/
|
|
155
|
+
interruptMode?: "immediate" | "wait";
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Optional session identifier forwarded to LLM providers.
|
|
159
|
+
* Used by providers that support session-based caching (e.g., OpenAI Codex).
|
|
160
|
+
*/
|
|
161
|
+
sessionId?: string;
|
|
162
|
+
|
|
163
|
+
/** Absolute wall-clock deadline in Unix epoch milliseconds. */
|
|
164
|
+
deadline?: number;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Optional resolver called per LLM request to produce request metadata.
|
|
168
|
+
* When set, the agent loop evaluates it **after** `getApiKey` resolves the
|
|
169
|
+
* session-sticky credential, ensuring the metadata's `account_uuid` reflects
|
|
170
|
+
* the credential actually used for the request (not the credential that was
|
|
171
|
+
* current when `AgentLoopConfig` was first constructed). Overrides the static
|
|
172
|
+
* `metadata` field when present.
|
|
173
|
+
*/
|
|
174
|
+
metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
|
|
178
|
+
*
|
|
179
|
+
* Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage
|
|
180
|
+
* that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,
|
|
181
|
+
* status messages) should be filtered out.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* convertToLlm: (messages) => messages.flatMap(m => {
|
|
186
|
+
* if (m.role === "custom") {
|
|
187
|
+
* // Convert custom message to user message
|
|
188
|
+
* return [{ role: "user", content: m.content, timestamp: m.timestamp }];
|
|
189
|
+
* }
|
|
190
|
+
* if (m.role === "notification") {
|
|
191
|
+
* // Filter out UI-only messages
|
|
192
|
+
* return [];
|
|
193
|
+
* }
|
|
194
|
+
* // Pass through standard LLM messages
|
|
195
|
+
* return [m];
|
|
196
|
+
* })
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Optional transform applied to the context before `convertToLlm`.
|
|
203
|
+
*
|
|
204
|
+
* Use this for operations that work at the AgentMessage level:
|
|
205
|
+
* - Context window management (pruning old messages)
|
|
206
|
+
* - Injecting context from external sources
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* transformContext: async (messages) => {
|
|
211
|
+
* if (estimateTokens(messages) > MAX_TOKENS) {
|
|
212
|
+
* return pruneOldMessages(messages);
|
|
213
|
+
* }
|
|
214
|
+
* return messages;
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Optional transform applied to the final provider context after conversion,
|
|
222
|
+
* normalization, and append-only context handling, but before telemetry capture
|
|
223
|
+
* and provider send.
|
|
224
|
+
*/
|
|
225
|
+
transformProviderContext?: (context: Context, model: Model) => Context | Promise<Context>;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Resolves the API key or resolver for the current model before each LLM call.
|
|
229
|
+
*
|
|
230
|
+
* Returning an ApiKeyResolver lets the stream retry policy refresh or rotate
|
|
231
|
+
* the model-scoped credential after auth/usage-limit errors.
|
|
232
|
+
*/
|
|
233
|
+
getApiKey?: (model: Model) => Promise<ApiKey | undefined> | ApiKey | undefined;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Returns steering messages to inject into the conversation mid-run.
|
|
237
|
+
*
|
|
238
|
+
* Called at injection boundaries only (loop start and after a tool batch
|
|
239
|
+
* fully settles), so dequeued messages are immediately injected. The
|
|
240
|
+
* mid-batch interrupt poll uses {@link hasSteeringMessages} instead and
|
|
241
|
+
* never consumes the queue.
|
|
242
|
+
*/
|
|
243
|
+
getSteeringMessages?: (signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Peeks whether steering messages are queued, without consuming them.
|
|
247
|
+
*
|
|
248
|
+
* Called after each tool execution (unless interruptMode is "wait") to decide
|
|
249
|
+
* whether to skip the remaining tool calls in the batch. The queue keeps
|
|
250
|
+
* owning its messages until the loop reaches the next injection boundary and
|
|
251
|
+
* dequeues via {@link getSteeringMessages} — so callers can still cancel or
|
|
252
|
+
* restore queued messages while in-flight tools settle, and an external
|
|
253
|
+
* abort in that window leaves the queue intact for a post-abort continue.
|
|
254
|
+
*
|
|
255
|
+
* Returning `true` is treated as user-originated steering for compatibility.
|
|
256
|
+
* Return a {@link SteeringQueueState} when the queue can distinguish system
|
|
257
|
+
* advisories from real user messages.
|
|
258
|
+
*
|
|
259
|
+
* When omitted, steering never interrupts a running tool batch; queued
|
|
260
|
+
* messages are still delivered at the next injection boundary.
|
|
261
|
+
*/
|
|
262
|
+
hasSteeringMessages?: () => boolean | SteeringQueueState | Promise<boolean | SteeringQueueState>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Wakes the in-flight tool interrupt watcher when a steering message is queued.
|
|
266
|
+
* The callback must not consume the queue; the loop still calls
|
|
267
|
+
* {@link hasSteeringMessages} before aborting and injects through
|
|
268
|
+
* {@link getSteeringMessages}.
|
|
269
|
+
*/
|
|
270
|
+
waitForSteeringMessages?: (signal?: AbortSignal) => Promise<void>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Peeks whether IRC messages should interrupt an interruptible waiting tool.
|
|
274
|
+
*
|
|
275
|
+
* Uses the same delivery rules as steering: the poll is non-consuming, only
|
|
276
|
+
* runs for interruptible tools, and is ignored when interruptMode is "wait".
|
|
277
|
+
* The host owns message injection at the next boundary.
|
|
278
|
+
*/
|
|
279
|
+
hasIrcInterrupts?: () => boolean | Promise<boolean>;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Returns follow-up messages to process after the agent would otherwise stop.
|
|
283
|
+
*
|
|
284
|
+
* Called when the agent has no more tool calls and no steering messages.
|
|
285
|
+
* If messages are returned, they're added to the context and the agent
|
|
286
|
+
* continues with another turn.
|
|
287
|
+
*/
|
|
288
|
+
getFollowUpMessages?: (signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
289
|
+
/**
|
|
290
|
+
* Returns non-interrupting "aside" messages to inject at a step boundary.
|
|
291
|
+
*
|
|
292
|
+
* Polled after each tool batch (before the next LLM call) AND at the yield
|
|
293
|
+
* check. Unlike steering, these NEVER abort in-flight tools — they are passive
|
|
294
|
+
* notifications (e.g. background-job completions, late LSP diagnostics) that
|
|
295
|
+
* should reach the model between requests without waiting for the agent to
|
|
296
|
+
* fully stop. Returned messages are appended to the context with normal
|
|
297
|
+
* message events and keep the loop running so the model can react.
|
|
298
|
+
*/
|
|
299
|
+
getAsideMessages?: () => Promise<AsideMessage[]>;
|
|
300
|
+
/**
|
|
301
|
+
* Hook fired right before the loop would exit.
|
|
302
|
+
*
|
|
303
|
+
* Called when the agent has no more tool calls and no steering messages,
|
|
304
|
+
* immediately before polling follow-up messages.
|
|
305
|
+
*/
|
|
306
|
+
onBeforeYield?: () => Promise<void> | void;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Provides tool execution context, resolved per tool call.
|
|
310
|
+
* Use for late-bound UI or session state access.
|
|
311
|
+
*/
|
|
312
|
+
getToolContext?: (toolCall?: ToolCallContext) => AgentToolContext | undefined;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Refreshes prompt/tool context from live session state before each model call.
|
|
316
|
+
* Use this when tool availability or the system prompt can change mid-turn.
|
|
317
|
+
*
|
|
318
|
+
* Runs after pending messages are folded in and before provider conversion.
|
|
319
|
+
* Mutate the agent context here; use `beforeModelCall` to inspect the
|
|
320
|
+
* provider-bound context.
|
|
321
|
+
*/
|
|
322
|
+
syncContextBeforeModelCall?: (context: AgentContext, signal?: AbortSignal) => void | Promise<void>;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Asked after the complete provider context has been built, including
|
|
326
|
+
* message conversion, provider transforms, normalized tools, and owned
|
|
327
|
+
* dialect prompt injection. Returning {@link AgentPreModelCallStop} ends
|
|
328
|
+
* the stream without emitting `turn_start`, so no turn is left open and no
|
|
329
|
+
* request is billed. Return nothing to proceed. The signal aborts when
|
|
330
|
+
* the run is canceled or its deadline expires.
|
|
331
|
+
*/
|
|
332
|
+
beforeModelCall?: AgentBeforeModelCall;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Optional transform applied to tool call arguments before execution.
|
|
336
|
+
* Use for deobfuscating secrets or rewriting arguments.
|
|
337
|
+
*/
|
|
338
|
+
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
339
|
+
/**
|
|
340
|
+
* Resolve a tool call whose name matched no advertised tool (including
|
|
341
|
+
* `customWireName` aliases). Lets hosts route calls to tools they expose
|
|
342
|
+
* through side transports (e.g. `xd://` device mounts) instead of failing
|
|
343
|
+
* with "Tool not found". Returning `undefined` keeps the failure.
|
|
344
|
+
*/
|
|
345
|
+
resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Enable intent tracing for tool calls.
|
|
349
|
+
* When enabled, the harness injects a `string` field into tool schemas sent to the model,
|
|
350
|
+
* then strips from arguments before executing tools.
|
|
351
|
+
*/
|
|
352
|
+
intentTracing?: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Strip tool descriptions (top-level + nested schema annotations) from the
|
|
355
|
+
* provider-bound tool specs. Use when the full catalog is rendered into the
|
|
356
|
+
* system prompt instead, so descriptions are not duplicated on the wire.
|
|
357
|
+
*/
|
|
358
|
+
pruneToolDescriptions?: boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Owned tool calling dialect.
|
|
361
|
+
*
|
|
362
|
+
* Undefined keeps provider-native tool calling. A dialect value sends no
|
|
363
|
+
* native `tools`, forces `toolChoice` off, appends that dialect's tool catalog
|
|
364
|
+
* instructions, re-encodes prior tool calls/results as text, and parses the
|
|
365
|
+
* model's text output back into canonical `toolCall` blocks.
|
|
366
|
+
*/
|
|
367
|
+
dialect?: Dialect;
|
|
368
|
+
/**
|
|
369
|
+
* When owned (in-band) tool calling is active and the model starts
|
|
370
|
+
* fabricating a tool result inside its own turn, control how the loop reacts:
|
|
371
|
+
* - `true` (default): abort the provider request immediately so it stops
|
|
372
|
+
* generating the hallucinated continuation (cheaper, lower latency).
|
|
373
|
+
* - `false`: let the request finish and silently discard everything past the
|
|
374
|
+
* fabrication boundary (keeps the connection alive but pays for the tokens
|
|
375
|
+
* the model spends on the discarded tail).
|
|
376
|
+
* Only meaningful when {@link dialect} (or `PI_DIALECT`) selects an
|
|
377
|
+
* owned dialect; native tool calling never fabricates results in text.
|
|
378
|
+
*/
|
|
379
|
+
abortOnFabricatedToolResult?: boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Append-only context mode — stabilizes system prompt + tool spec bytes
|
|
382
|
+
* across turns so provider prefix caches hit at maximum rate.
|
|
383
|
+
*
|
|
384
|
+
* When set, the loop reads messages from the append-only log (stable
|
|
385
|
+
* byte prefix) and caches system prompt + tools. Tools exclude per-turn
|
|
386
|
+
* `i` intent fields.
|
|
387
|
+
*/
|
|
388
|
+
appendOnlyContext?: AppendOnlyContextManager;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Inspect assistant streaming events before they are published to the outer agent event stream.
|
|
392
|
+
* Callers may abort synchronously to stop consuming buffered provider events.
|
|
393
|
+
*/
|
|
394
|
+
onAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Called when GPT-5 Harmony protocol leakage is detected and mitigated.
|
|
398
|
+
*/
|
|
399
|
+
onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Dynamic tool-choice directive, resolved once per turn. Returns a hard
|
|
403
|
+
* {@link ToolChoice} (applied verbatim, overriding the static `toolChoice`),
|
|
404
|
+
* a {@link SoftToolRequirement} (the loop reminds-then-escalates instead of
|
|
405
|
+
* forcing `tool_choice` immediately, so a model that complies with the
|
|
406
|
+
* reminder pays no message-cache invalidation), or `undefined` to fall back
|
|
407
|
+
* to the static `toolChoice`.
|
|
408
|
+
*/
|
|
409
|
+
getToolChoice?: () => ToolChoiceDirective | undefined;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Soft-requirement lifecycle retained by the host when a pre-model-call
|
|
413
|
+
* gate stops a run before its pending reminder or escalation is served.
|
|
414
|
+
*/
|
|
415
|
+
softToolRequirementState?: SoftToolRequirementState;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Notifies the host that the pre-model-call gate stopped the run after a
|
|
419
|
+
* hard tool choice was obtained from {@link getToolChoice} but before it
|
|
420
|
+
* was served, so the host can retain it for the next admitted request.
|
|
421
|
+
*/
|
|
422
|
+
onToolChoiceRejected?: () => void;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Dynamic reasoning effort override, resolved per LLM call.
|
|
426
|
+
* When set and returns a value, overrides the static `reasoning` captured
|
|
427
|
+
* at run-loop start. Use this so mid-run thinking-level changes apply on
|
|
428
|
+
* the next model call instead of waiting for the next prompt.
|
|
429
|
+
*/
|
|
430
|
+
getReasoning?: () => Effort | undefined;
|
|
431
|
+
/**
|
|
432
|
+
* Dynamic model override, resolved once per LLM call. When set, each
|
|
433
|
+
* provider call re-reads the model (like {@link getReasoning}) so mid-run
|
|
434
|
+
* model switches — context promotion, retry fallback — apply on the next
|
|
435
|
+
* call instead of the run finishing on the stale model captured at
|
|
436
|
+
* run-loop start. Falls back to the static {@link model} when unset.
|
|
437
|
+
*/
|
|
438
|
+
getModel?: () => Model;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Dynamic reasoning-disable override, resolved per LLM call. When set,
|
|
442
|
+
* its return value overrides the static `disableReasoning` from
|
|
443
|
+
* `SimpleStreamOptions` for that request. Pair with `getReasoning` so
|
|
444
|
+
* mid-run transitions into and out of the explicit `off` state propagate
|
|
445
|
+
* to the next provider call.
|
|
446
|
+
*/
|
|
447
|
+
getDisableReasoning?: () => boolean | undefined;
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Per-call effective service-tier resolver. Unlike {@link getReasoning},
|
|
451
|
+
* this is *authoritative*: when set, its return value (including
|
|
452
|
+
* `undefined`) fully replaces the static `serviceTier` for the request and
|
|
453
|
+
* its telemetry. The resolver receives the model being requested so the
|
|
454
|
+
* caller can scope the tier per provider/model without mutating the shared
|
|
455
|
+
* session `serviceTier` (e.g. opting a Fireworks model into the Priority
|
|
456
|
+
* serving path while leaving the OpenAI/Anthropic tier untouched).
|
|
457
|
+
*/
|
|
458
|
+
getServiceTier?: (model: Model) => ServiceTier | undefined;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Per-call working-directory resolver, read once per LLM call. When set, its
|
|
462
|
+
* return value overrides the static {@link SimpleStreamOptions.cwd} for the
|
|
463
|
+
* request (falling back to that static `cwd` when it returns `undefined`).
|
|
464
|
+
* Lets the host reflect a session move (`/move`, which updates the working
|
|
465
|
+
* directory without reconstructing the loop config) into provider options —
|
|
466
|
+
* e.g. GitLab Duo Agent namespace/project discovery keys off this cwd's git
|
|
467
|
+
* remote, so a stale value would strand discovery on the original repo.
|
|
468
|
+
*/
|
|
469
|
+
getCwd?: () => string | undefined;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Called once per tool call after argument validation, in call order, before
|
|
473
|
+
* the call is scheduled — ahead of concurrency resolution,
|
|
474
|
+
* `tool_execution_start`, and `tool.execute`. On the streamed path it runs
|
|
475
|
+
* before the assistant message's `message_start`/`message_end` are emitted.
|
|
476
|
+
*
|
|
477
|
+
* Return `{ block: true }` to prevent execution. The loop emits an error tool
|
|
478
|
+
* result instead (using `reason` as the error text, or a default if omitted).
|
|
479
|
+
*
|
|
480
|
+
* Return `{ args }` to replace the arguments the call runs with. The
|
|
481
|
+
* replacement is revalidated against the tool schema and written back to the
|
|
482
|
+
* tool-call block, making it the single source of truth: history, execution
|
|
483
|
+
* events, persistence, provider replay, concurrency scheduling, and
|
|
484
|
+
* `tool.execute` all see the revised arguments. Mutating `context.args` in
|
|
485
|
+
* place also survives into execution, but a returned `args` object wins.
|
|
486
|
+
*
|
|
487
|
+
* The hook receives the run's request abort signal and is responsible for
|
|
488
|
+
* honoring it. Throwing surfaces as a tool-error result and does not abort the
|
|
489
|
+
* rest of the batch.
|
|
490
|
+
*/
|
|
491
|
+
beforeToolCall?: (
|
|
492
|
+
context: BeforeToolCallContext,
|
|
493
|
+
signal?: AbortSignal,
|
|
494
|
+
) => Promise<BeforeToolCallResult | undefined> | BeforeToolCallResult | undefined;
|
|
495
|
+
/**
|
|
496
|
+
* Called after a turn ends and before the loop polls steering/asides for the
|
|
497
|
+
* next iteration. `context` carries the just-finished turn; `context.willContinue`
|
|
498
|
+
* is true when the current tool-loop batch is continuing without yielding to
|
|
499
|
+
* post-turn steering.
|
|
500
|
+
*/
|
|
501
|
+
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal, context?: AgentTurnEndContext) => Promise<void> | void;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Called once an assistant message is finalized from the model stream, before
|
|
505
|
+
* it is appended to the context, emitted as `message_end`, or its tool calls
|
|
506
|
+
* are validated and dispatched. The hook may mutate the message in place —
|
|
507
|
+
* both its text content and its tool-call arguments — and those edits are seen
|
|
508
|
+
* by the transcript, the UI, and tool execution alike (single source of truth).
|
|
509
|
+
*
|
|
510
|
+
* Used for inline macro expansion: rewriting `@[[runtime.name(args)]]` tokens
|
|
511
|
+
* to host-computed values before anything downstream consumes the message.
|
|
512
|
+
* Runs at most once per assistant message; must not throw (a throw would abort
|
|
513
|
+
* the turn).
|
|
514
|
+
*/
|
|
515
|
+
transformAssistantMessage?: (message: AssistantMessage, signal?: AbortSignal) => Promise<void> | void;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Called after a tool finishes executing, before `tool_execution_end` and the
|
|
519
|
+
* tool-result message are emitted.
|
|
520
|
+
*
|
|
521
|
+
* Return an `AfterToolCallResult` to override individual fields of the executed
|
|
522
|
+
* tool result. Omitted fields keep their original values; there is no deep merge.
|
|
523
|
+
*
|
|
524
|
+
* Throwing surfaces as a tool-error result and does not abort the rest of the batch.
|
|
525
|
+
*/
|
|
526
|
+
afterToolCall?: (
|
|
527
|
+
context: AfterToolCallContext,
|
|
528
|
+
signal?: AbortSignal,
|
|
529
|
+
) => Promise<AfterToolCallResult | undefined> | AfterToolCallResult | undefined;
|
|
530
|
+
/**
|
|
531
|
+
* Opt-in OpenTelemetry instrumentation. Passing `{}` enables the loop's
|
|
532
|
+
* GenAI-semantic-convention spans (`invoke_agent`, `chat`, `execute_tool`)
|
|
533
|
+
* using the global tracer provider. Leaving this field undefined disables
|
|
534
|
+
* the instrumentation entirely — the loop performs zero tracer lookups.
|
|
535
|
+
*
|
|
536
|
+
* See {@link AgentTelemetryConfig} for the full surface (hooks, content
|
|
537
|
+
* capture, cost estimator, agent identity).
|
|
538
|
+
*/
|
|
539
|
+
telemetry?: AgentTelemetryConfig;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Batch/sequencing metadata for the tool call currently being processed.
|
|
544
|
+
*/
|
|
545
|
+
export interface ToolCallContext {
|
|
546
|
+
batchId: string;
|
|
547
|
+
index: number;
|
|
548
|
+
total: number;
|
|
549
|
+
toolCalls: Array<{ id: string; name: string }>;
|
|
550
|
+
/** Provider-native metadata for the current call, when present. */
|
|
551
|
+
providerMetadata?: ToolCallProviderMetadata;
|
|
552
|
+
/**
|
|
553
|
+
* Cooperative steering signal: aborted when a queued user/steering message
|
|
554
|
+
* (or an interrupting peer IRC) is detected while this tool batch runs.
|
|
555
|
+
* Unlike the hard abort signal it NEVER kills the tool — long-running
|
|
556
|
+
* tools MAY observe it (via `ctx.toolCall.steeringSignal`) to finish early
|
|
557
|
+
* or background themselves so the message injects promptly; ignoring it is
|
|
558
|
+
* always safe (the message injects at the next batch boundary).
|
|
559
|
+
*/
|
|
560
|
+
steeringSignal?: AbortSignal;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** A single tool-call content block emitted by an assistant message. */
|
|
564
|
+
export type AgentToolCall = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Result returned from `beforeToolCall`.
|
|
568
|
+
*
|
|
569
|
+
* Set `block: true` to prevent the tool from executing. The loop emits an error tool
|
|
570
|
+
* result instead, using `reason` as the error text (or a default if omitted).
|
|
571
|
+
*
|
|
572
|
+
* Set `args` to replace the tool-call arguments. The replacement is revalidated
|
|
573
|
+
* against the tool schema (a failure surfaces as a validation-error tool result),
|
|
574
|
+
* written back to the tool-call block on the assistant message, and seen by
|
|
575
|
+
* history, scheduling, execution events, and `tool.execute` alike. It is
|
|
576
|
+
* ignored when `block` is true.
|
|
577
|
+
*/
|
|
578
|
+
export interface BeforeToolCallResult {
|
|
579
|
+
block?: boolean;
|
|
580
|
+
reason?: string;
|
|
581
|
+
args?: Record<string, unknown>;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Partial override returned from `afterToolCall`.
|
|
586
|
+
*
|
|
587
|
+
* Merge semantics are field-by-field; omitted fields keep the executed values.
|
|
588
|
+
* No deep merge is performed.
|
|
589
|
+
*/
|
|
590
|
+
export interface AfterToolCallResult {
|
|
591
|
+
/** If provided, replaces the tool result content array in full. */
|
|
592
|
+
content?: (TextContent | ImageContent)[];
|
|
593
|
+
/** If provided, replaces the tool result details payload in full. */
|
|
594
|
+
details?: unknown;
|
|
595
|
+
/** If provided, replaces the provider-native result metadata in full. */
|
|
596
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
597
|
+
/** If provided, replaces the error flag carried with the tool result. */
|
|
598
|
+
isError?: boolean;
|
|
599
|
+
/** If provided, replaces the contextually-useless flag carried with the tool result. */
|
|
600
|
+
useless?: boolean;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Context passed to `beforeToolCall`. */
|
|
604
|
+
export interface BeforeToolCallContext {
|
|
605
|
+
/** The assistant message that requested the tool call. */
|
|
606
|
+
assistantMessage: AssistantMessage;
|
|
607
|
+
/** The raw tool call block from `assistantMessage.content`. */
|
|
608
|
+
toolCall: AgentToolCall;
|
|
609
|
+
/** The resolved tool the call dispatches to. */
|
|
610
|
+
tool: AgentTool<any>;
|
|
611
|
+
/**
|
|
612
|
+
* Validated tool arguments. The same reference is forwarded to `tool.execute`
|
|
613
|
+
* (after any `transformToolCallArguments` pass), so in-place mutations stick;
|
|
614
|
+
* a returned `BeforeToolCallResult.args` replaces them entirely.
|
|
615
|
+
*/
|
|
616
|
+
args: Record<string, unknown>;
|
|
617
|
+
/** Current agent context at the time the tool call is prepared. */
|
|
618
|
+
context: AgentContext;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Context passed to `afterToolCall`. */
|
|
622
|
+
export interface AfterToolCallContext {
|
|
623
|
+
/** The assistant message that requested the tool call. */
|
|
624
|
+
assistantMessage: AssistantMessage;
|
|
625
|
+
/** The raw tool call block from `assistantMessage.content`. */
|
|
626
|
+
toolCall: AgentToolCall;
|
|
627
|
+
/** Validated tool arguments used for execution (post `beforeToolCall` mutations). */
|
|
628
|
+
args: Record<string, unknown>;
|
|
629
|
+
/** The executed tool result before any `afterToolCall` overrides are applied. */
|
|
630
|
+
result: AgentToolResult<any>;
|
|
631
|
+
/** Whether the executed tool result is currently treated as an error. */
|
|
632
|
+
isError: boolean;
|
|
633
|
+
/** Current agent context at the time the tool call is finalized. */
|
|
634
|
+
context: AgentContext;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Extensible interface for custom app messages.
|
|
639
|
+
* Apps can extend via declaration merging:
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```typescript
|
|
643
|
+
* declare module "@linxiraos/pi-agent-core" {
|
|
644
|
+
* interface CustomAgentMessages {
|
|
645
|
+
* artifact: ArtifactMessage;
|
|
646
|
+
* notification: NotificationMessage;
|
|
647
|
+
* }
|
|
648
|
+
* }
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
export interface CustomAgentMessages {
|
|
652
|
+
// Empty by default - apps extend via declaration merging
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* AgentMessage: Union of LLM messages + custom messages.
|
|
657
|
+
* This abstraction allows apps to add custom message types while maintaining
|
|
658
|
+
* type safety and compatibility with the base LLM messages.
|
|
659
|
+
*/
|
|
660
|
+
export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Agent state containing all configuration and conversation data.
|
|
664
|
+
*/
|
|
665
|
+
export interface AgentState {
|
|
666
|
+
systemPrompt: string[];
|
|
667
|
+
model: Model;
|
|
668
|
+
thinkingLevel?: Effort;
|
|
669
|
+
disableReasoning?: boolean;
|
|
670
|
+
tools: AgentTool<any>[];
|
|
671
|
+
messages: AgentMessage[]; // Can include attachments + custom message types
|
|
672
|
+
isStreaming: boolean;
|
|
673
|
+
streamMessage: AgentMessage | null;
|
|
674
|
+
pendingToolCalls: Set<string>;
|
|
675
|
+
error?: string;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export interface AgentToolResult<T = any, _TInput = unknown> {
|
|
679
|
+
// Content blocks supporting text and images
|
|
680
|
+
content: (TextContent | ImageContent)[];
|
|
681
|
+
// Details to be displayed in a UI or logged
|
|
682
|
+
details?: T;
|
|
683
|
+
// Marks a non-throwing failure (e.g. an aggregator catching per-entry errors).
|
|
684
|
+
// agent-loop honors this and surfaces it as a tool error on the wire.
|
|
685
|
+
isError?: boolean;
|
|
686
|
+
/** Provider-native metadata that must survive into history replay unchanged. */
|
|
687
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
688
|
+
/** Marks the result as contextually useless: safe for compaction to elide once consumed (e.g. zero matches, wait timeout). Ignored when isError is set. */
|
|
689
|
+
useless?: boolean;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Callback for streaming tool execution updates
|
|
693
|
+
export type AgentToolUpdateCallback<T = any, TInput = unknown> = (partialResult: AgentToolResult<T, TInput>) => void;
|
|
694
|
+
|
|
695
|
+
/** Options passed to renderResult */
|
|
696
|
+
export interface RenderResultOptions {
|
|
697
|
+
/** Whether the result view is expanded */
|
|
698
|
+
expanded: boolean;
|
|
699
|
+
/** Whether this is a partial/streaming result */
|
|
700
|
+
isPartial: boolean;
|
|
701
|
+
/** Current spinner frame index for animated elements (optional) */
|
|
702
|
+
spinnerFrame?: number;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Capability tier a tool exercises. Determines which approval modes auto-approve it. */
|
|
706
|
+
export type ToolTier = "read" | "write" | "exec";
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* How an enabled tool is presented to the model. `"essential"` tools are exposed
|
|
710
|
+
* as normal top-level tools. `"discoverable"` tools are removed from the top-level
|
|
711
|
+
* schema and either mounted under `xd://` device URLs (when that transport is
|
|
712
|
+
* active) or surfaced through BM25 tool search — keeping their schemas off every
|
|
713
|
+
* request. Selection (settings, `hidden`, `defaultInactive`, explicit `--tools`,
|
|
714
|
+
* provider availability) decides whether a tool is enabled; `loadMode` only
|
|
715
|
+
* decides how an enabled tool is presented.
|
|
716
|
+
*/
|
|
717
|
+
export type ToolLoadMode = "essential" | "discoverable";
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Per-tool approval declaration.
|
|
721
|
+
* - bare tier ("read" / "write" / "exec") — static classification.
|
|
722
|
+
* - object form — adds a `reason` (shown in the prompt) and/or `override: true`
|
|
723
|
+
* (force-prompt even in modes that would otherwise auto-approve this tier).
|
|
724
|
+
* `policy: "deny"` blocks the call at the approval gate.
|
|
725
|
+
* - function — dynamic, given parsed args. Returns either form above.
|
|
726
|
+
*
|
|
727
|
+
* Omitted approvals are treated as "exec" by callers that enforce approvals.
|
|
728
|
+
*/
|
|
729
|
+
export type ToolApprovalDecision =
|
|
730
|
+
| ToolTier
|
|
731
|
+
| { tier: ToolTier; reason?: string; override?: boolean; policy?: "allow" | "deny" | "prompt" };
|
|
732
|
+
export type ToolApproval = ToolApprovalDecision | ((args: unknown) => ToolApprovalDecision);
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Context passed to tool execution.
|
|
736
|
+
* Apps can extend via declaration merging.
|
|
737
|
+
*/
|
|
738
|
+
export interface AgentToolContext {
|
|
739
|
+
// Empty by default - apps extend via declaration merging
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export type AgentToolExecFn<TParameters extends TSchema = TSchema, TDetails = any, TTheme = unknown> = (
|
|
743
|
+
this: AgentTool<TParameters, TDetails, TTheme>,
|
|
744
|
+
toolCallId: string,
|
|
745
|
+
params: Static<TParameters>,
|
|
746
|
+
signal?: AbortSignal,
|
|
747
|
+
onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>,
|
|
748
|
+
context?: AgentToolContext,
|
|
749
|
+
) => Promise<AgentToolResult<TDetails, TParameters>>;
|
|
750
|
+
|
|
751
|
+
// AgentTool extends Tool but adds the execute function
|
|
752
|
+
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any, TTheme = unknown>
|
|
753
|
+
extends Tool<TParameters> {
|
|
754
|
+
// A human-readable label for the tool to be displayed in UI
|
|
755
|
+
label: string;
|
|
756
|
+
/** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
|
|
757
|
+
hidden?: boolean;
|
|
758
|
+
/** If true, tool can stage a pending action that requires explicit resolution via the resolve tool. */
|
|
759
|
+
deferrable?: boolean;
|
|
760
|
+
/** How an enabled tool is presented. See {@link ToolLoadMode}. Omitted is treated as `"essential"` for built-ins; custom-tool adapters normalize omission to `"discoverable"`. */
|
|
761
|
+
loadMode?: ToolLoadMode;
|
|
762
|
+
/** Short one-line summary used for tool discovery indexes. */
|
|
763
|
+
summary?: string;
|
|
764
|
+
/**
|
|
765
|
+
* Concurrency mode for tool scheduling when multiple calls are in one turn.
|
|
766
|
+
* - "shared": can run alongside other shared tools (default)
|
|
767
|
+
* - "exclusive": runs alone; other tools wait until it finishes
|
|
768
|
+
* - function: resolved per call from the (raw, pre-validation) arguments
|
|
769
|
+
*/
|
|
770
|
+
concurrency?: "shared" | "exclusive" | ((args: Partial<Static<TParameters>>) => "shared" | "exclusive");
|
|
771
|
+
/** If true, argument validation errors are non-fatal: raw args are passed to execute() instead of returning an error to the LLM. */
|
|
772
|
+
lenientArgValidation?: boolean;
|
|
773
|
+
/**
|
|
774
|
+
* Whether the agent loop may abort this tool mid-execution to deliver a
|
|
775
|
+
* queued steering message. A function resolves this per call from the raw,
|
|
776
|
+
* pre-validation arguments.
|
|
777
|
+
*
|
|
778
|
+
* Enable only for calls that purely *wait* and observe their abort signal
|
|
779
|
+
* cleanly (e.g. `job` poll), so the abort surfaces the tool's current
|
|
780
|
+
* snapshot rather than corrupting a side effect. Honored only when
|
|
781
|
+
* `interruptMode` is "immediate".
|
|
782
|
+
*/
|
|
783
|
+
interruptible?: boolean | ((args: Partial<Static<TParameters>>) => boolean);
|
|
784
|
+
/**
|
|
785
|
+
* Controls how the INTENT_FIELD (`i`) is handled for this tool.
|
|
786
|
+
* - `"require"` (default): `i` is injected and required in the parameter schema.
|
|
787
|
+
* - `"optional"`: `i` is injected as an optional/nullable field.
|
|
788
|
+
* - `"omit"`: `i` is NOT injected. Use for tools where intent is obvious (yield, resolve, todo, …).
|
|
789
|
+
* - function: `i` is NOT injected; intent is derived dynamically from (potentially partial / streaming) args.
|
|
790
|
+
*/
|
|
791
|
+
intent?: "omit" | "optional" | "require" | ((args: Partial<Static<TParameters>>) => string | undefined);
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Normalize (potentially partial) streamed arguments into the plain text that
|
|
795
|
+
* stream-content matchers (e.g. TTSR rules) should inspect — the real content
|
|
796
|
+
* the call introduces, without wire grammar such as patch prefixes or JSON
|
|
797
|
+
* string escaping. Return `undefined` to fall back to raw argument-delta
|
|
798
|
+
* matching.
|
|
799
|
+
*/
|
|
800
|
+
matcherDigest?: (args: unknown) => string | undefined;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Surface the target file paths a (potentially partial) streamed call would
|
|
804
|
+
* touch, so path-scoped stream matchers (e.g. TTSR `tool:edit(*.ts)` globs)
|
|
805
|
+
* can match without a top-level `path`/`paths` argument. Used for tools whose
|
|
806
|
+
* wire grammar embeds paths inside the streamed payload (hashline section
|
|
807
|
+
* headers, apply_patch envelope markers). Return `undefined` (or an empty
|
|
808
|
+
* array) to fall back to the caller's top-level argument scan.
|
|
809
|
+
*/
|
|
810
|
+
matcherPaths?: (args: unknown) => readonly string[] | undefined;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Per-file projection of a (potentially partial) streamed call, pairing each
|
|
814
|
+
* touched file path with the digest of only the lines added to that file.
|
|
815
|
+
* Path-scoped stream matchers (TTSR) evaluate each entry in isolation, so a
|
|
816
|
+
* scoped rule like `tool:edit(*.ts)` never fires on text that actually
|
|
817
|
+
* belongs to a sibling Markdown hunk in a multi-file payload. Takes
|
|
818
|
+
* precedence over {@link matcherDigest} + {@link matcherPaths} when present;
|
|
819
|
+
* returns `undefined` (or empty) to fall back to the combined hooks.
|
|
820
|
+
*/
|
|
821
|
+
matcherEntries?: (args: unknown) => readonly { path: string; digest: string }[] | undefined;
|
|
822
|
+
|
|
823
|
+
/** Capability tier declaration used by approval gates. Omitted means "exec". */
|
|
824
|
+
approval?: ToolApproval;
|
|
825
|
+
|
|
826
|
+
/** Lines appended after the standard approval prompt header. */
|
|
827
|
+
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
828
|
+
|
|
829
|
+
/** The main execution callback for this tool. */
|
|
830
|
+
execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
|
|
831
|
+
|
|
832
|
+
/** Optional custom rendering for tool call display (returns UI component) */
|
|
833
|
+
renderCall?: (args: Static<TParameters>, options: RenderResultOptions, theme: TTheme) => unknown;
|
|
834
|
+
|
|
835
|
+
/** Optional custom rendering for tool result display (returns UI component) */
|
|
836
|
+
renderResult?: (
|
|
837
|
+
result: AgentToolResult<TDetails, TParameters>,
|
|
838
|
+
options: RenderResultOptions,
|
|
839
|
+
theme: TTheme,
|
|
840
|
+
) => unknown;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// AgentContext is like Context but uses AgentTool
|
|
844
|
+
export interface AgentContext {
|
|
845
|
+
systemPrompt: string[];
|
|
846
|
+
messages: AgentMessage[];
|
|
847
|
+
tools?: AgentTool<any>[];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Events emitted by the Agent for UI updates.
|
|
852
|
+
* These events provide fine-grained lifecycle information for messages, turns, and tool executions.
|
|
853
|
+
*/
|
|
854
|
+
export type AgentEvent =
|
|
855
|
+
// Agent lifecycle
|
|
856
|
+
| { type: "agent_start" }
|
|
857
|
+
| {
|
|
858
|
+
type: "agent_end";
|
|
859
|
+
messages: AgentMessage[];
|
|
860
|
+
/** Present iff `AgentTelemetryConfig` was supplied on this run. */
|
|
861
|
+
telemetry?: AgentRunSummary;
|
|
862
|
+
coverage?: AgentRunCoverage;
|
|
863
|
+
}
|
|
864
|
+
// Turn lifecycle - a turn is one assistant response + any tool calls/results
|
|
865
|
+
| { type: "turn_start" }
|
|
866
|
+
| { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
|
|
867
|
+
// Message lifecycle - emitted for user, assistant, and toolResult messages
|
|
868
|
+
| { type: "message_start"; message: AgentMessage }
|
|
869
|
+
// Only emitted for assistant messages during streaming
|
|
870
|
+
| { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
|
|
871
|
+
| { type: "message_end"; message: AgentMessage }
|
|
872
|
+
// Tool execution lifecycle
|
|
873
|
+
| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any; intent?: string }
|
|
874
|
+
| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
|
|
875
|
+
| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError?: boolean };
|