import type {
CanonicalModelEvent,
CanonicalModelRequest,
CanonicalToolCall,
CanonicalUsage,
MultimodalConstraints,
} from "../../model/index.js";
import type {
PermissionContext,
PermissionMode,
PermissionResult,
} from "../../permission/index.js";
import type { PilotDeckToolAuditRecorder } from "../audit/ToolAuditRecorder.js";
import type { PilotDeckElicitationChannel } from "../elicitation/PilotDeckElicitationChannel.js";
import type { PilotDeckToolInputSchema, PilotDeckToolValidationResult } from "./schema.js";
* File-history sink used by `edit_file` / `write_file` to backup files
* before mutation (C4 §6.4 / F1 trackEdit). Wired in by the agent loop
* when a `FileHistoryStore` is available; absent for stand-alone tool
* runtimes (tests, scripted invocations) — affected tools tolerate the
* missing sink and proceed without backups.
*/
export type PilotDeckToolFileHistorySink = {
trackEdit(filePath: string, messageId: string): Promise<void>;
};
* Minimal model client surface tools may use to issue secondary model calls
* (e.g. `agent` subagent prompts, `web_fetch` content extraction). Mirrors
* `AgentModelRuntime` but lives in the tool protocol to avoid a tool→agent
* dependency cycle.
*/
export type PilotDeckToolModelClient = {
stream(request: CanonicalModelRequest, signal?: AbortSignal): AsyncIterable<CanonicalModelEvent>;
};
* Subagent fork API exposed to the `agent` tool by the AgentLoop. Lives in
* the tool protocol layer so the tool implementation doesn't reach into
* `agent/sub/*` directly (which would invert the dependency).
*
* `depth` reports the *current* subagent fork depth (0 = top-level agent;
* each `agent` invocation hands the next-level loop `depth + 1`).
* `maxSubagentDepth` is the cap (default 1) — the `agent` tool raises
* `subagent_depth_exceeded` when `depth >= maxSubagentDepth`.
*/
export type PilotDeckSubagentForkApi = {
depth: number;
maxSubagentDepth: number;
listDefinitions(): { id: string; description: string }[];
isAllowedDefinition(id: string): boolean;
fork(args: {
definitionId: string;
directive: string;
subagentId: string;
abortSignal?: AbortSignal;
timeoutMs?: number;
}): Promise<{
markdown: string;
usage: CanonicalUsage;
turns: number;
durationMs: number;
parsed?: Record<string, string>;
}>;
};
export type PilotDeckToolKind =
| "filesystem"
| "shell"
| "network"
| "mcp"
| "session"
| "agent"
| "structured_output"
| "custom";
export type PilotDeckToolResultContent =
| { type: "text"; text: string }
| { type: "json"; value: unknown }
| { type: "image"; mimeType: string; data: string; bytes?: number; detail?: "auto" | "low" | "high" }
| { type: "pdf"; mimeType: "application/pdf"; data: string; bytes: number; pages?: number }
| { type: "file"; path: string; mimeType?: string; description?: string };
export type PilotDeckReadFileStateEntry = {
mtimeMs: number;
kind: "text" | "image" | "pdf" | "notebook";
offset?: number;
limit?: number;
pages?: string;
};
export type PilotDeckReadFileStateMap = Map<string, PilotDeckReadFileStateEntry>;
export type PilotDeckWriteSnapshotEntry = {
absolutePath: string;
mtimeMs: number;
contentHash: string;
offset?: number;
limit?: number;
};
export type PilotDeckWriteSnapshotMap = Map<string, PilotDeckWriteSnapshotEntry>;
export type PilotDeckFileUpdateNotification = {
absolutePath: string;
relativePath: string;
root: string;
content: string;
previousContent: string | null;
};
export type PilotDeckFileUpdateNotifier = {
didChange?(update: PilotDeckFileUpdateNotification): Promise<void> | void;
didSave?(update: PilotDeckFileUpdateNotification): Promise<void> | void;
};
export type PilotDeckToolSupplementalMessage = {
role: "user";
content: PilotDeckToolResultContent[];
isMeta?: boolean;
};
export type PilotDeckToolExecutionOutput<Output = unknown> = {
content: PilotDeckToolResultContent[];
supplementalMessages?: PilotDeckToolSupplementalMessage[];
data?: Output;
metadata?: Record<string, unknown>;
};
* Tool progress event emitted via `PilotDeckToolRuntimeContext.progress`.
* The sink is fire-and-forget — progress events MUST NOT replace the final
* `tool_result`, MUST NOT enter the durable transcript, and MAY be dropped
* by the caller without affecting tool correctness.
*/
export type PilotDeckToolProgressEvent = {
type: "tool_progress";
sessionId: string;
turnId: string;
toolCallId: string;
toolName: string;
message: string;
metadata?: Record<string, unknown>;
createdAt: string;
};
export type PilotDeckToolProgressSink = (event: PilotDeckToolProgressEvent) => void;
export type PilotDeckTodoItem = {
id?: string;
content: string;
status: "pending" | "in_progress" | "completed";
priority?: string;
};
export type PilotDeckPlanTodoStateSnapshot = {
approvedPlan?: string;
requiresInitialization: boolean;
requiresRefresh: boolean;
lastMarkdown?: string;
todos: PilotDeckTodoItem[];
};
export type PilotDeckPlanTodoStateHandle = {
getSnapshot(): PilotDeckPlanTodoStateSnapshot;
markPlanApproved(plan: string): void;
recordTodoWrite(markdown: string, todos: PilotDeckTodoItem[]): void;
markToolProgressChanged(toolName: string): void;
buildPromptAddendum(): string | undefined;
blockingMessageFor(toolName: string, isReadOnly: boolean): string | undefined;
};
export type PilotDeckToolRuntimeContext = {
sessionId: string;
turnId: string;
cwd: string;
abortSignal?: AbortSignal;
subagentTimeoutMs?: number;
permissionMode: PermissionMode;
permissionContext: PermissionContext;
auditRecorder?: PilotDeckToolAuditRecorder;
now?: () => Date;
env?: NodeJS.ProcessEnv;
maxResultBytes?: number;
* Optional streaming progress sink. Tools that produce incremental output
* (e.g. `bash` stdout/stderr chunks) can call this to emit progress events
* before the final result lands. Absent by default; callers opt in by
* supplying a sink.
*/
progress?: PilotDeckToolProgressSink;
* Optional model client for tools that need to issue secondary model calls
* (e.g. `agent` subagent prompts, `web_fetch` content extraction). Absent
* when the caller didn't provide one — affected tools must report
* `unsupported_tool` with a clear hint instead of failing silently.
*/
model?: PilotDeckToolModelClient;
* Optional user-elicitation channel used by `ask_user_question` and any
* tool that requests a synchronous user answer. The host (Gateway / TUI /
* CLI / Feishu) wires this in. Absent when no UI is connected; affected
* tools must report `unsupported_tool`.
*/
elicitation?: PilotDeckElicitationChannel;
* Optional file-history sink (C4). When provided, `edit_file` /
* `write_file` call `trackEdit(filePath, messageId)` *before* mutating,
* so a later `pilotdeck rewind` can restore the prior content. Absent
* for stand-alone runtimes; tools tolerate the absence by simply
* skipping backup capture (intentional — never block the edit on
* snapshot infrastructure).
*/
fileHistory?: PilotDeckToolFileHistorySink;
* Optional opaque "message id" the file-history sink uses to group
* snapshots. Set by the agent loop per user turn (typically the user
* message UUID). When `fileHistory` is set but `messageId` is missing,
* tools fall back to `turnId` so trackEdit still runs.
*/
messageId?: string;
* Subagent fork depth (C2 §6.2 / S?). Top-level agent runs at depth 0;
* subagent forks pass `depth + 1`. The `agent` tool throws
* `subagent_depth_exceeded` when invoked at `depth >= maxSubagentDepth`
* (default 1, blocking nested forks). Absent → treated as 0.
*/
subagentDepth?: number;
* Subagent fork API (C2 §6.2). Wired in by the AgentLoop when the parent
* supports forking; absent for stand-alone tool runtimes (tests). When
* absent, the `agent` tool falls back to the legacy single-shot model
* call so unit tests still work.
*/
subagent?: PilotDeckSubagentForkApi;
* Plan directory handle for plan-mode tools (`enter_plan_mode` /
* `exit_plan_mode`). When plan mode is active the model may create and
* edit markdown files under this directory, then submit one explicitly
* via `exit_plan_mode(plan_file_path)`. Absent when PlanFileManager is
* not configured (e.g. headless / test runtimes).
*/
planDirectory?: {
path: string;
resolve(filePath: string): string | undefined;
read(filePath: string): string | undefined;
};
* Optional session-scoped todo state used by plan execution flows. The
* `todo_write` tool records checklist updates here; the runtime can enforce
* that side-effecting tools do not run before the checklist is initialized
* or refreshed after progress changes.
*/
planTodo?: PilotDeckPlanTodoStateHandle;
* Multimodal constraints of the model driving this agent session.
* Absent when the model config doesn't declare multimodal capabilities
* (text-only). Tools use this to decide whether to return rich content
* (e.g. base64 images) or a text-only fallback description.
*/
modelMultimodal?: MultimodalConstraints;
* Current max output tokens for this session's model. Surfaced in
* validation error hints so the model can reason about output budget
* when planning multi-step writes.
*/
maxOutputTokens?: number;
* True when the model's response was truncated due to output token limit
* (finishReason === "length"). Tools use this to produce accurate error
* messages — e.g. distinguishing "parameter missing because output was
* truncated" from "model failed to provide required parameter".
*/
outputTruncated?: boolean;
* Optional session-scoped cache for read_file de-duplication. The agent loop
* keeps the map stable across turns so repeated reads of an unchanged file
* can return a lightweight stub instead of re-injecting the full payload.
*/
readFileState?: PilotDeckReadFileStateMap;
* Optional session-scoped map of full-text reads that may authorize
* subsequent write_file overwrites. Only complete text reads populate this.
*/
writeSnapshots?: PilotDeckWriteSnapshotMap;
* Optional sink that propagates successful file writes to host integrations
* such as LSP bridges or editor diff views.
*/
fileUpdateNotifier?: PilotDeckFileUpdateNotifier;
};
export type PilotDeckToolDefinition<Input = unknown, Output = unknown> = {
name: string;
aliases?: string[];
title?: string;
description: string;
kind: PilotDeckToolKind;
inputSchema: PilotDeckToolInputSchema;
outputSchema?: Record<string, unknown>;
maxResultBytes?: number;
shouldDefer?: boolean;
alwaysLoad?: boolean;
searchHint?: string;
isReadOnly(input: Input): boolean;
isConcurrencySafe(input: Input): boolean;
isDestructive?(input: Input): boolean;
requiresUserInteraction?(input: Input): boolean;
isOpenWorld?(input: Input): boolean;
validateInput?(input: Input, context: PilotDeckToolRuntimeContext): Promise<PilotDeckToolValidationResult>;
checkPermissions?(input: Input, context: PilotDeckToolRuntimeContext): Promise<PermissionResult>;
execute(input: Input, context: PilotDeckToolRuntimeContext): Promise<PilotDeckToolExecutionOutput<Output>>;
};
export type PilotDeckToolCall = CanonicalToolCall;