mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): preserve extension tools with defaults
This commit is contained in:
parent
4d9aa837c2
commit
541045ae0e
5 changed files with 82 additions and 19 deletions
|
|
@ -7,7 +7,7 @@
|
|||
- Added fullscreen transcript search with `Ctrl+Shift+F`, incremental match highlighting, configurable search match theme colors, and next/previous navigation with `Enter`/`Ctrl+G` and `Shift+Enter`/`Ctrl+Shift+G`.
|
||||
- Added experimental strict JSON-schema constrained sampling for the default `read`, `bash`, `edit`, and `write` tools under `PI_EXPERIMENTAL=1`.
|
||||
- Added a fullscreen exit output setting to choose between printing the final transcript and only a session resume hint.
|
||||
- Added the `defaultTools` setting for configuring the initial tool allowlist globally or per project.
|
||||
- Added the `defaultTools` setting for configuring the initial built-in tool selection globally or per project.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -204,9 +204,9 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
|
|||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `defaultTools` | string[] | - | Initial tool allowlist. When omitted, Pi uses its standard defaults |
|
||||
| `defaultTools` | string[] | - | Built-in tools enabled initially. When omitted, Pi uses its standard defaults |
|
||||
|
||||
`defaultTools` applies to built-in, extension, and custom tools, like the `--tools` CLI option:
|
||||
`defaultTools` selects the built-in tools enabled at startup. Extension and SDK custom tools remain enabled:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -214,7 +214,7 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
|
|||
}
|
||||
```
|
||||
|
||||
An empty array starts with no tools. `--tools`, `--no-tools`, and `--no-builtin-tools` override this setting; `--exclude-tools` filters the resulting list. A project `defaultTools` array replaces the global array.
|
||||
An empty array starts with no built-in tools while preserving extension and SDK custom tools. `--tools` replaces this behavior with a strict allowlist for all tools, `--no-tools` disables all tools, and `--no-builtin-tools` disables the built-in defaults. `--exclude-tools` filters the resulting list. A project `defaultTools` array replaces the global array.
|
||||
|
||||
### Sessions
|
||||
|
||||
|
|
|
|||
|
|
@ -62,10 +62,11 @@ export interface CreateAgentSessionOptions {
|
|||
/**
|
||||
* Optional allowlist of tool names.
|
||||
*
|
||||
* When omitted, pi uses the `defaultTools` setting when configured. Otherwise it
|
||||
* enables the default built-in tools (read, bash, edit, write) and leaves
|
||||
* extension/custom tools enabled unless `noTools` changes that default. When
|
||||
* provided, only the listed tool names are enabled.
|
||||
* When omitted, pi uses the `defaultTools` setting for the initial built-in
|
||||
* selection when configured. Otherwise it enables the default built-in tools
|
||||
* (read, bash, edit, write). Extension/custom tools remain enabled unless
|
||||
* `noTools` changes that default. When provided, only the listed tool names are
|
||||
* enabled.
|
||||
*/
|
||||
tools?: string[];
|
||||
/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */
|
||||
|
|
@ -245,9 +246,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
|
||||
const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write"];
|
||||
const configuredDefaultToolNames = settingsManager.getDefaultTools();
|
||||
const allowedToolNames =
|
||||
options.tools ??
|
||||
(options.noTools === "all" ? [] : options.noTools === undefined ? configuredDefaultToolNames : undefined);
|
||||
const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
|
||||
const excludedToolNames = options.excludeTools;
|
||||
const excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;
|
||||
const initialActiveToolNames = (
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export interface Settings {
|
|||
terminal?: TerminalSettings;
|
||||
images?: ImageSettings;
|
||||
enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
|
||||
defaultTools?: string[]; // Initial tool allowlist (same format as --tools CLI flag)
|
||||
defaultTools?: string[]; // Initial built-in tool selection
|
||||
doubleEscapeAction?: "fork" | "tree" | "none"; // Action for double-escape with empty editor (default: "tree")
|
||||
treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; // Default filter when opening /tree
|
||||
thinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@earendil-works/pi-ai/compat";
|
||||
import { Type } from "typebox";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { createAgentSessionFromServices, createAgentSessionServices } from "../src/core/agent-session-services.ts";
|
||||
import { DefaultResourceLoader } from "../src/core/resource-loader.ts";
|
||||
import { type CreateAgentSessionOptions, createAgentSession } from "../src/core/sdk.ts";
|
||||
import { type CreateAgentSessionOptions, createAgentSession, type InlineExtension } from "../src/core/sdk.ts";
|
||||
import { SessionManager } from "../src/core/session-manager.ts";
|
||||
import { SettingsManager } from "../src/core/settings-manager.ts";
|
||||
|
||||
type ToolOptions = Pick<CreateAgentSessionOptions, "tools" | "excludeTools" | "noTools">;
|
||||
type ToolOptions = Pick<CreateAgentSessionOptions, "tools" | "excludeTools" | "noTools" | "customTools">;
|
||||
|
||||
describe("defaultTools setting", () => {
|
||||
let tempDir: string;
|
||||
|
|
@ -27,9 +28,18 @@ describe("defaultTools setting", () => {
|
|||
}
|
||||
});
|
||||
|
||||
async function createSession(defaultTools: string[], options: ToolOptions = {}) {
|
||||
async function createSession(
|
||||
defaultTools: string[],
|
||||
options: ToolOptions = {},
|
||||
extensionFactories: InlineExtension[] = [],
|
||||
) {
|
||||
const settingsManager = SettingsManager.inMemory({ defaultTools });
|
||||
const resourceLoader = new DefaultResourceLoader({ cwd: tempDir, agentDir, settingsManager });
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
cwd: tempDir,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories,
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
|
||||
return (
|
||||
|
|
@ -45,16 +55,65 @@ describe("defaultTools setting", () => {
|
|||
).session;
|
||||
}
|
||||
|
||||
it("uses the configured list as the initial tool allowlist", async () => {
|
||||
it("uses the configured list as the initial built-in selection", async () => {
|
||||
const session = await createSession(["grep", "find"]);
|
||||
|
||||
expect(session.getAllTools().map((tool) => tool.name)).toEqual(["grep", "find"]);
|
||||
expect(
|
||||
session
|
||||
.getAllTools()
|
||||
.map((tool) => tool.name)
|
||||
.sort(),
|
||||
).toEqual(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
||||
expect(session.getActiveToolNames()).toEqual(["grep", "find"]);
|
||||
expect(session.systemPrompt).toContain("- grep:");
|
||||
expect(session.systemPrompt).not.toContain("- read:");
|
||||
session.dispose();
|
||||
});
|
||||
|
||||
it("keeps extension and SDK custom tools enabled", async () => {
|
||||
const session = await createSession(
|
||||
["grep"],
|
||||
{
|
||||
customTools: [
|
||||
{
|
||||
name: "sdk_tool",
|
||||
label: "SDK Tool",
|
||||
description: "SDK custom tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
},
|
||||
],
|
||||
},
|
||||
[
|
||||
(pi) => {
|
||||
pi.registerTool({
|
||||
name: "static_tool",
|
||||
label: "Static Tool",
|
||||
description: "Statically registered extension tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
});
|
||||
pi.on("session_start", () => {
|
||||
pi.registerTool({
|
||||
name: "dynamic_tool",
|
||||
label: "Dynamic Tool",
|
||||
description: "Dynamically registered extension tool",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
);
|
||||
await session.bindExtensions({});
|
||||
|
||||
expect(session.getActiveToolNames().sort()).toEqual(["dynamic_tool", "grep", "sdk_tool", "static_tool"]);
|
||||
expect(session.getAllTools().map((tool) => tool.name)).toEqual(
|
||||
expect.arrayContaining(["read", "dynamic_tool", "sdk_tool", "static_tool"]),
|
||||
);
|
||||
session.dispose();
|
||||
});
|
||||
|
||||
it("preserves explicit tool option precedence", async () => {
|
||||
const allowlistedSession = await createSession(["grep"], { tools: ["read"] });
|
||||
expect(allowlistedSession.getActiveToolNames()).toEqual(["read"]);
|
||||
|
|
@ -79,7 +138,12 @@ describe("defaultTools setting", () => {
|
|||
model: getModel("anthropic", "claude-sonnet-4-5")!,
|
||||
});
|
||||
|
||||
expect(session.getAllTools().map((tool) => tool.name)).toEqual(["ls"]);
|
||||
expect(
|
||||
session
|
||||
.getAllTools()
|
||||
.map((tool) => tool.name)
|
||||
.sort(),
|
||||
).toEqual(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
||||
expect(session.getActiveToolNames()).toEqual(["ls"]);
|
||||
session.dispose();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue