feat(coding-agent): expose session metadata to bash tools (#6967)

This commit is contained in:
Armin Ronacher
2026-07-22 20:15:02 +02:00
committed by GitHub
parent c55ae2faa5
commit bb3d7d399c
16 changed files with 278 additions and 27 deletions
@@ -694,6 +694,10 @@ export class ExtensionRunner {
runner.assertActive();
return getModel();
},
get thinkingLevel() {
runner.assertActive();
return runner.runtime.getThinkingLevel();
},
isIdle: () => {
runner.assertActive();
return runner.isIdleFn();
@@ -317,6 +317,8 @@ export interface ExtensionContext {
modelRegistry: ModelRegistry;
/** Current model (may be undefined) */
model: Model<any> | undefined;
/** Current thinking level, when provided by the session runtime. */
thinkingLevel?: ThinkingLevel;
/** Whether the agent is idle (not streaming) */
isIdle(): boolean;
/** Whether project-local trust is active for this context. */
@@ -133,7 +133,7 @@ Pi documentation (read only when the user asks about pi itself, its SDK, extensi
- Additional docs: ${docsPath}
- Examples: ${examplesPath} (extensions, custom tools, SDK)
- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)
- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;
+41 -6
View File
@@ -15,7 +15,7 @@ import {
trackDetachedChildPid,
untrackDetachedChildPid,
} from "../../utils/shell.ts";
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts";
import type { ExtensionContext, ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts";
import { OutputAccumulator } from "./output-accumulator.ts";
import { getTextOutput, invalidArgText, str } from "./render-utils.ts";
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
@@ -155,8 +155,31 @@ export interface BashSpawnContext {
export type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;
function resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {
const baseContext: BashSpawnContext = { command, cwd, env: { ...getShellEnv() } };
function resolveSpawnContext(
command: string,
cwd: string,
spawnHook: BashSpawnHook | undefined,
exposeSessionEnvironment: boolean,
ctx: ExtensionContext | undefined,
): BashSpawnContext {
const env = { ...getShellEnv() };
delete env.PI_SESSION_ID;
delete env.PI_SESSION_FILE;
delete env.PI_PROVIDER;
delete env.PI_MODEL;
delete env.PI_REASONING_LEVEL;
if (exposeSessionEnvironment && ctx) {
const model = ctx.model;
env.PI_SESSION_ID = ctx.sessionManager.getSessionId();
const sessionFile = ctx.sessionManager.getSessionFile();
if (sessionFile) env.PI_SESSION_FILE = sessionFile;
if (model) {
env.PI_PROVIDER = model.provider;
env.PI_MODEL = model.id;
}
if (ctx.thinkingLevel) env.PI_REASONING_LEVEL = ctx.thinkingLevel;
}
const baseContext: BashSpawnContext = { command, cwd, env };
return spawnHook ? spawnHook(baseContext) : baseContext;
}
@@ -167,6 +190,8 @@ export interface BashToolOptions {
commandPrefix?: string;
/** Optional explicit shell path from settings */
shellPath?: string;
/** Expose current Pi session metadata as PI_* environment variables. Default: true */
exposeSessionEnvironment?: boolean;
/** Hook to adjust command, cwd, or env before execution */
spawnHook?: BashSpawnHook;
}
@@ -294,22 +319,26 @@ export function createBashToolDefinition(
): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {
const ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath });
const commandPrefix = options?.commandPrefix;
const exposeSessionEnvironment = options?.exposeSessionEnvironment ?? true;
const spawnHook = options?.spawnHook;
return {
name: "bash",
label: "bash",
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
promptSnippet: "Execute bash commands (ls, grep, find, etc.)",
promptGuidelines: exposeSessionEnvironment
? ["Inspect PI_* environment variables for current model and session details."]
: undefined,
parameters: bashSchema,
async execute(
_toolCallId,
{ command, timeout }: { command: string; timeout?: number },
signal?: AbortSignal,
onUpdate?,
_ctx?,
ctx?,
) {
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook, exposeSessionEnvironment, ctx);
const output = new OutputAccumulator({ tempFilePrefix: "pi-bash" });
let acceptingOutput = true;
let updateTimer: NodeJS.Timeout | undefined;
@@ -466,5 +495,11 @@ export function createBashToolDefinition(
}
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
return wrapToolDefinition(createBashToolDefinition(cwd, options));
const definition = createBashToolDefinition(cwd, options);
const tool = wrapToolDefinition(definition);
Object.assign(tool, {
promptSnippet: definition.promptSnippet,
promptGuidelines: definition.promptGuidelines,
});
return tool;
}
@@ -13,8 +13,8 @@ export function wrapToolDefinition<TDetails = unknown>(
parameters: definition.parameters,
prepareArguments: definition.prepareArguments,
executionMode: definition.executionMode,
execute: (toolCallId, params, signal, onUpdate) =>
definition.execute(toolCallId, params, signal, onUpdate, ctxFactory?.() as ExtensionContext),
execute: (toolCallId, params, signal, onUpdate, ctx?: ExtensionContext) =>
definition.execute(toolCallId, params, signal, onUpdate, ctx ?? (ctxFactory?.() as ExtensionContext)),
};
}
@@ -1783,6 +1783,7 @@ export class InteractiveMode {
sessionManager: this.sessionManager,
modelRegistry: extensionRunner.getModelRegistry(),
model: this.session.model,
thinkingLevel: this.session.thinkingLevel,
isIdle: () => this.session.isIdle,
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
signal: this.session.agent.signal,